diff --git a/.eslintrc.js b/.eslintrc.js index 3161a25b70870..5802f67a7cd65 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,9 +17,6 @@ * under the License. */ -const { readdirSync } = require('fs'); -const { resolve } = require('path'); - const APACHE_2_0_LICENSE_HEADER = ` /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -288,7 +285,7 @@ module.exports = { }, { target: [ - '(src|x-pack)/legacy/**/*', + 'src/legacy/**/*', '(src|x-pack)/plugins/**/(public|server)/**/*', 'examples/**/*', ], @@ -319,14 +316,11 @@ module.exports = { }, { target: [ - '(src|x-pack)/legacy/**/*', + 'src/legacy/**/*', '(src|x-pack)/plugins/**/(public|server)/**/*', 'examples/**/*', '!(src|x-pack)/**/*.test.*', '!(x-pack/)?test/**/*', - // next folder contains legacy browser tests which can't be migrated to jest - // which import np files - '!src/legacy/core_plugins/kibana/public/__tests__/**/*', ], from: [ '(src|x-pack)/plugins/**/(public|server)/**/*', @@ -341,14 +335,6 @@ module.exports = { '(src|x-pack)/plugins/**/*', '!(src|x-pack)/plugins/**/server/**/*', - 'src/legacy/core_plugins/**/*', - '!src/legacy/core_plugins/**/server/**/*', - '!src/legacy/core_plugins/**/index.{js,mjs,ts,tsx}', - - 'x-pack/legacy/plugins/**/*', - '!x-pack/legacy/plugins/**/server/**/*', - '!x-pack/legacy/plugins/**/index.{js,mjs,ts,tsx}', - 'examples/**/*', '!examples/**/server/**/*', ], @@ -370,12 +356,7 @@ module.exports = { }, { target: ['src/core/**/*'], - from: [ - 'plugins/**/*', - 'src/plugins/**/*', - 'src/legacy/core_plugins/**/*', - 'src/legacy/ui/**/*', - ], + from: ['plugins/**/*', 'src/plugins/**/*', 'src/legacy/ui/**/*'], errorMessage: 'The core cannot depend on any plugins.', }, { @@ -388,12 +369,6 @@ module.exports = { target: [ 'test/plugin_functional/plugins/**/public/np_ready/**/*', 'test/plugin_functional/plugins/**/server/np_ready/**/*', - 'src/legacy/core_plugins/**/public/np_ready/**/*', - 'src/legacy/core_plugins/vis_type_*/public/**/*', - '!src/legacy/core_plugins/vis_type_*/public/legacy*', - 'src/legacy/core_plugins/**/server/np_ready/**/*', - 'x-pack/legacy/plugins/**/public/np_ready/**/*', - 'x-pack/legacy/plugins/**/server/np_ready/**/*', ], allowSameFolder: true, errorMessage: @@ -443,22 +418,14 @@ module.exports = { settings: { // instructs import/no-extraneous-dependencies to treat certain modules // as core modules, even if they aren't listed in package.json - 'import/core-modules': ['plugins', 'legacy/ui'], + 'import/core-modules': ['plugins'], 'import/resolver': { '@kbn/eslint-import-resolver-kibana': { forceNode: false, rootPackageName: 'kibana', kibanaPath: '.', - pluginMap: readdirSync(resolve(__dirname, 'x-pack/legacy/plugins')).reduce( - (acc, name) => { - if (!name.startsWith('_')) { - acc[name] = `x-pack/legacy/plugins/${name}`; - } - return acc; - }, - {} - ), + pluginMap: {}, }, }, }, @@ -764,16 +731,6 @@ module.exports = { }, }, - /** - * GIS overrides - */ - { - files: ['x-pack/legacy/plugins/maps/**/*.js'], - rules: { - 'react/prefer-stateless-function': [0, { ignorePureComponents: false }], - }, - }, - /** * ML overrides */ @@ -812,7 +769,7 @@ module.exports = { }, { // typescript only for front and back end - files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{ts,tsx}'], + files: ['x-pack/plugins/security_solution/**/*.{ts,tsx}'], rules: { // This will be turned on after bug fixes are complete // '@typescript-eslint/explicit-member-accessibility': 'warn', @@ -858,7 +815,7 @@ module.exports = { // }, { // typescript and javascript for front and back end - files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{js,mjs,ts,tsx}'], + files: ['x-pack/plugins/security_solution/**/*.{js,mjs,ts,tsx}'], plugins: ['eslint-plugin-node', 'react'], env: { mocha: true, @@ -1089,7 +1046,7 @@ module.exports = { { // typescript only for front and back end files: [ - 'x-pack/{,legacy/}plugins/{alerts,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}', + 'x-pack/plugins/{alerts,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}', ], rules: { '@typescript-eslint/no-explicit-any': 'error', @@ -1238,10 +1195,7 @@ module.exports = { * TSVB overrides */ { - files: [ - 'src/plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}', - 'src/legacy/core_plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}', - ], + files: ['src/plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}'], rules: { 'import/no-default-export': 'error', }, @@ -1275,5 +1229,20 @@ module.exports = { '@typescript-eslint/prefer-ts-expect-error': 'error', }, }, + { + files: [ + '**/public/**/*.{js,mjs,ts,tsx}', + '**/common/**/*.{js,mjs,ts,tsx}', + 'packages/**/*.{js,mjs,ts,tsx}', + ], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: ['lodash/*', '!lodash/fp'], + }, + ], + }, + }, ], }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0bdddddab8de5..2f5e14f1f1599 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,10 +7,12 @@ /x-pack/plugins/discover_enhanced/ @elastic/kibana-app /x-pack/plugins/lens/ @elastic/kibana-app /x-pack/plugins/graph/ @elastic/kibana-app +/src/plugins/advanced_settings/ @elastic/kibana-app /src/plugins/charts/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app /src/plugins/input_control_vis/ @elastic/kibana-app +/src/plugins/management/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/vis_default_editor/ @elastic/kibana-app /src/plugins/vis_type_markdown/ @elastic/kibana-app @@ -23,6 +25,7 @@ /src/plugins/vis_type_vislib/ @elastic/kibana-app /src/plugins/vis_type_xy/ @elastic/kibana-app /src/plugins/visualize/ @elastic/kibana-app +/src/plugins/visualizations/ @elastic/kibana-app # App Architecture /examples/bfetch_explorer/ @elastic/kibana-app-arch @@ -38,7 +41,6 @@ /examples/url_generators_explorer/ @elastic/kibana-app-arch /packages/elastic-datemath/ @elastic/kibana-app-arch /packages/kbn-interpreter/ @elastic/kibana-app-arch -/src/plugins/advanced_settings/ @elastic/kibana-app-arch /src/plugins/bfetch/ @elastic/kibana-app-arch /src/plugins/data/ @elastic/kibana-app-arch /src/plugins/embeddable/ @elastic/kibana-app-arch @@ -47,11 +49,9 @@ /src/plugins/kibana_react/ @elastic/kibana-app-arch /src/plugins/kibana_react/public/code_editor @elastic/kibana-canvas /src/plugins/kibana_utils/ @elastic/kibana-app-arch -/src/plugins/management/ @elastic/kibana-app-arch /src/plugins/navigation/ @elastic/kibana-app-arch /src/plugins/share/ @elastic/kibana-app-arch /src/plugins/ui_actions/ @elastic/kibana-app-arch -/src/plugins/visualizations/ @elastic/kibana-app-arch /x-pack/examples/ui_actions_enhanced_examples/ @elastic/kibana-app-arch /x-pack/plugins/data_enhanced/ @elastic/kibana-app-arch /x-pack/plugins/embeddable_enhanced/ @elastic/kibana-app-arch @@ -65,14 +65,15 @@ # Client Side Monitoring (lives in APM directories but owned by Uptime) /x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm @elastic/uptime +/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature @elastic/uptime /x-pack/plugins/apm/public/application/csmApp.tsx @elastic/uptime /x-pack/plugins/apm/public/components/app/RumDashboard @elastic/uptime /x-pack/plugins/apm/server/lib/rum_client @elastic/uptime /x-pack/plugins/apm/server/routes/rum_client.ts @elastic/uptime -/x-pack/plugins/apm/server/projections/rum_overview.ts @elastic/uptime +/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @elastic/uptime # Beats -/x-pack/legacy/plugins/beats_management/ @elastic/beats +/x-pack/plugins/beats_management/ @elastic/beats # Canvas /x-pack/plugins/canvas/ @elastic/kibana-canvas @@ -86,16 +87,13 @@ /x-pack/plugins/global_search_bar/ @elastic/kibana-core-ui # Observability UIs -/x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui /x-pack/plugins/infra/ @elastic/logs-metrics-ui /x-pack/plugins/ingest_manager/ @elastic/ingest-management -/x-pack/legacy/plugins/ingest_manager/ @elastic/ingest-management /x-pack/plugins/observability/ @elastic/observability-ui /x-pack/plugins/monitoring/ @elastic/stack-monitoring-ui /x-pack/plugins/uptime @elastic/uptime # Machine Learning -/x-pack/legacy/plugins/ml/ @elastic/ml-ui /x-pack/plugins/ml/ @elastic/ml-ui /x-pack/test/functional/apps/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/machine_learning/ @elastic/ml-ui @@ -107,7 +105,6 @@ /x-pack/test/functional/services/transform.ts @elastic/ml-ui # Maps -/x-pack/legacy/plugins/maps/ @elastic/kibana-gis /x-pack/plugins/maps/ @elastic/kibana-gis /x-pack/test/api_integration/apis/maps/ @elastic/kibana-gis /x-pack/test/functional/apps/maps/ @elastic/kibana-gis @@ -234,13 +231,8 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib /src/plugins/dev_tools/ @elastic/es-ui /src/plugins/console/ @elastic/es-ui /src/plugins/es_ui_shared/ @elastic/es-ui -/x-pack/legacy/plugins/cross_cluster_replication/ @elastic/es-ui +/x-pack/plugins/cross_cluster_replication/ @elastic/es-ui /x-pack/plugins/index_lifecycle_management/ @elastic/es-ui -/x-pack/legacy/plugins/index_management/ @elastic/es-ui -/x-pack/legacy/plugins/license_management/ @elastic/es-ui -/x-pack/legacy/plugins/rollup/ @elastic/es-ui -/x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui -/x-pack/legacy/plugins/upgrade_assistant/ @elastic/es-ui /x-pack/plugins/console_extensions/ @elastic/es-ui /x-pack/plugins/es_ui_shared/ @elastic/es-ui /x-pack/plugins/grokdebugger/ @elastic/es-ui @@ -254,6 +246,8 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib /x-pack/plugins/upgrade_assistant/ @elastic/es-ui /x-pack/plugins/watcher/ @elastic/es-ui /x-pack/plugins/ingest_pipelines/ @elastic/es-ui +/packages/kbn-ace/ @elastic/es-ui +/packages/kbn-monaco/ @elastic/es-ui # Endpoint /x-pack/plugins/endpoint/ @elastic/endpoint-app-team @elastic/siem diff --git a/.telemetryrc.json b/.telemetryrc.json index 818f9805628e1..d3446b45033ee 100644 --- a/.telemetryrc.json +++ b/.telemetryrc.json @@ -6,10 +6,7 @@ "src/plugins/kibana_react/", "src/plugins/testbed/", "src/plugins/kibana_utils/", - "src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts", - "src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts", - "src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts", - "src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts" + "src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts" ] } ] diff --git a/docs/api/saved-objects/create.asciidoc b/docs/api/saved-objects/create.asciidoc index 900f87ec29d8a..e6f3301bfea2b 100644 --- a/docs/api/saved-objects/create.asciidoc +++ b/docs/api/saved-objects/create.asciidoc @@ -13,7 +13,7 @@ experimental[] Create {kib} saved objects. `POST :/api/saved_objects//` -`POST :/s//api/saved_objects/` +`POST :/s//saved_objects/` [[saved-objects-api-create-path-params]] ==== Path parameters diff --git a/docs/developer/contributing/development-ci-metrics.asciidoc b/docs/developer/contributing/development-ci-metrics.asciidoc new file mode 100644 index 0000000000000..d4d54f1da7b8b --- /dev/null +++ b/docs/developer/contributing/development-ci-metrics.asciidoc @@ -0,0 +1,65 @@ +[[ci-metrics]] +== CI Metrics + +In addition to running our tests, CI collects metrics about the Kibana build. These metrics are sent to an external service to track changes over time, and to provide PR authors insights into the impact of their changes. + + +[[ci-metric-types]] +=== Metric types + + +[[ci-metric-types-bundle-size-metrics]] +==== Bundle size + +These metrics help contributors know how they are impacting the size of the bundles Kibana creates, and help make sure that Kibana loads as fast as possible. + +[[ci-metric-page-load-bundle-size]] `page load bundle size` :: +The size of the entry file produced for each bundle/plugin. This file is always loaded on every page load, so it should be as small as possible. To reduce this metric you can put any code that isn't necessary on every page load behind an https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports[`async import()`]. ++ +Code that is shared statically with other plugins will contribute to the `page load bundle size` of that plugin. This includes exports from the `public/index.ts` file and any file referenced by the `extraPublicDirs` manifest property. + +[[ci-metric-async-chunks-size]] `async chunks size` :: +An "async chunk" is created for the files imported by each https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports[`async import()`] statement. This metric tracks the sum size of these chunks, in bytes, broken down by plugin/bundle id. You can think of this as the amount of code users will have to download if they access all the components/applications within a bundle. + +[[ci-metric-misc-asset-size]] `miscellaneous assets size` :: +A "miscellaneous asset" is anything that isn't an async chunk or entry chunk, often images. This metric tracks the sum size of these assets, in bytes, broken down by plugin/bundle id. + +[[ci-metric-bundle-module-count]] `@kbn/optimizer bundle module count` :: +The number of separate modules included in each bundle/plugin. This is the best indicator we have for how long a specific bundle will take to be built by the `@kbn/optimizer`, so we report it to help people know when they've imported a module which might include a surprising number of sub-modules. + + +[[ci-metric-types-distributable-size]] +==== Distributable size + +The size of the Kibana distributable is an essential metric as it not only contributes to the time it takes to download, but it also impacts time it takes to extract the archive once downloaded. + +There are several metrics that we don't report on PRs because gzip-compression produces different file sizes even when provided the same input, so this metric would regularly show changes even though PR authors hadn't made any relevant changes. + +All metrics are collected from the `tar.gz` archive produced for the linux platform. + +[[ci-metric-distributable-file-count]] `distributable file count` :: +The number of files included in the default distributable. + +[[ci-metric-oss-distributable-file-count]] `oss distributable file count` :: +The number of files included in the OSS distributable. + +[[ci-metric-distributable-size]] `distributable size` :: +The size, in bytes, of the default distributable. _(not reported on PRs)_ + +[[ci-metric-oss-distributable-size]] `oss distributable size` :: +The size, in bytes, of the OSS distributable. _(not reported on PRs)_ + + +[[ci-metric-types-saved-object-field-counts]] +==== Saved Object field counts + +Elasticsearch limits the number of fields in an index to 1000 by default, and we want to avoid raising that limit. + +[[ci-metric-saved-object-field-count]] `Saved Objects .kibana field count` :: +The number of saved object fields broken down by saved object type. + + +[[ci-metric-adding-new-metrics]] +=== Adding new metrics + +You can report new metrics by using the `CiStatsReporter` class provided by the `@kbn/dev-utils` package. This class is automatically configured on CI and its methods noop when running outside of CI. For more details checkout the {kib-repo}blob/{branch}/packages/kbn-dev-utils/src/ci_stats_reporter[`CiStatsReporter` readme]. \ No newline at end of file diff --git a/docs/developer/contributing/index.asciidoc b/docs/developer/contributing/index.asciidoc index 99ab83bc2f073..ecb37ffe9c97b 100644 --- a/docs/developer/contributing/index.asciidoc +++ b/docs/developer/contributing/index.asciidoc @@ -9,6 +9,7 @@ Read <> to get your environment up and running, the * <> * <> * <> +* <> * <> * <> * <> @@ -78,6 +79,8 @@ include::development-tests.asciidoc[leveloffset=+1] include::interpreting-ci-failures.asciidoc[leveloffset=+1] +include::development-ci-metrics.asciidoc[leveloffset=+1] + include::development-documentation.asciidoc[leveloffset=+1] include::development-pull-request.asciidoc[leveloffset=+1] diff --git a/docs/developer/getting-started/development-plugin-resources.asciidoc b/docs/developer/getting-started/development-plugin-resources.asciidoc index 8f81138b81ed7..1fe211c87c660 100644 --- a/docs/developer/getting-started/development-plugin-resources.asciidoc +++ b/docs/developer/getting-started/development-plugin-resources.asciidoc @@ -33,7 +33,7 @@ To enable TypeScript support, create a `tsconfig.json` file at the root of your ["source","js"] ----------- { - // extend {kib}'s tsconfig, or use your own settings + // extend Kibana's tsconfig, or use your own settings "extends": "../../kibana/tsconfig.json", // tell the TypeScript compiler where to find your source files diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index b426621fed296..5a4a60c2e628e 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -504,6 +504,10 @@ in their infrastructure. |Contains HTTP endpoints and UiSettings that are slated for removal. +|{kib-repo}blob/{branch}/x-pack/plugins/drilldowns/url_drilldown/README.md[urlDrilldown] +|NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin. + + |=== include::{kibana-root}/src/plugins/dashboard/README.asciidoc[leveloffset=+1] diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror._constructor_.md new file mode 100644 index 0000000000000..676f1a2c785f8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror._constructor_.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DuplicateIndexPatternError](./kibana-plugin-plugins-data-public.duplicateindexpatternerror.md) > [(constructor)](./kibana-plugin-plugins-data-public.duplicateindexpatternerror._constructor_.md) + +## DuplicateIndexPatternError.(constructor) + +Constructs a new instance of the `DuplicateIndexPatternError` class + +Signature: + +```typescript +constructor(message: string); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| message | string | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror.md new file mode 100644 index 0000000000000..7ed8f97976464 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DuplicateIndexPatternError](./kibana-plugin-plugins-data-public.duplicateindexpatternerror.md) + +## DuplicateIndexPatternError class + +Signature: + +```typescript +export declare class DuplicateIndexPatternError extends Error +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(message)](./kibana-plugin-plugins-data-public.duplicateindexpatternerror._constructor_.md) | | Constructs a new instance of the DuplicateIndexPatternError class | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md index fee34378339af..45cd088ee1203 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md @@ -7,7 +7,7 @@ Signature: ```typescript -export interface IEsSearchRequest extends IKibanaSearchRequest +export interface IEsSearchRequest extends IKibanaSearchRequest ``` ## Properties @@ -15,5 +15,4 @@ export interface IEsSearchRequest extends IKibanaSearchRequest | Property | Type | Description | | --- | --- | --- | | [indexType](./kibana-plugin-plugins-data-public.iessearchrequest.indextype.md) | string | | -| [params](./kibana-plugin-plugins-data-public.iessearchrequest.params.md) | ISearchRequestParams | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.params.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.params.md deleted file mode 100644 index 24107faa28e8c..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.params.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.md) > [params](./kibana-plugin-plugins-data-public.iessearchrequest.params.md) - -## IEsSearchRequest.params property - -Signature: - -```typescript -params?: ISearchRequestParams; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md deleted file mode 100644 index 56fb1a7519811..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-public.iessearchresponse.md) > [isRunning](./kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md) - -## IEsSearchResponse.isRunning property - -Indicates whether async search is still in flight - -Signature: - -```typescript -isRunning?: boolean; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md index 7c9a6aa702463..c8a372edbdb85 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md @@ -2,19 +2,10 @@ [Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-public.iessearchresponse.md) -## IEsSearchResponse interface +## IEsSearchResponse type Signature: ```typescript -export interface IEsSearchResponse extends IKibanaSearchResponse +export declare type IEsSearchResponse = IKibanaSearchResponse>; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [isPartial](./kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md) | boolean | Indicates whether the results returned are complete or partial | -| [isRunning](./kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md) | boolean | Indicates whether async search is still in flight | -| [rawResponse](./kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md) | SearchResponse<Source> | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md deleted file mode 100644 index f4648143ebc2e..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-public.iessearchresponse.md) > [rawResponse](./kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md) - -## IEsSearchResponse.rawResponse property - -Signature: - -```typescript -rawResponse: SearchResponse; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md index 2c131c6da9937..60ac95bc21af2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md @@ -7,8 +7,5 @@ Signature: ```typescript -fieldFormatMap?: Record; +fieldFormatMap?: Record | undefined>; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md new file mode 100644 index 0000000000000..7466e4b9cf658 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) > [getFormatterForField](./kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md) + +## IIndexPattern.getFormatterForField property + +Signature: + +```typescript +getFormatterForField?: (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md index 1cb89822eb605..ba77e659f0834 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md @@ -14,8 +14,9 @@ export interface IIndexPattern | Property | Type | Description | | --- | --- | --- | -| [fieldFormatMap](./kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md) | Record<string, {
id: string;
params: unknown;
}> | | +| [fieldFormatMap](./kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md) | Record<string, SerializedFieldFormat<unknown> | undefined> | | | [fields](./kibana-plugin-plugins-data-public.iindexpattern.fields.md) | IFieldType[] | | +| [getFormatterForField](./kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md) | (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat | | | [id](./kibana-plugin-plugins-data-public.iindexpattern.id.md) | string | | | [timeFieldName](./kibana-plugin-plugins-data-public.iindexpattern.timefieldname.md) | string | | | [title](./kibana-plugin-plugins-data-public.iindexpattern.title.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md index fd20f2944c5be..0fe62f575a927 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md @@ -9,7 +9,7 @@ ```typescript toSpec(options?: { getFormatterForField?: IndexPattern['getFormatterForField']; - }): FieldSpec[]; + }): IndexPatternFieldMap; ``` ## Parameters @@ -20,5 +20,5 @@ toSpec(options?: { Returns: -`FieldSpec[]` +`IndexPatternFieldMap` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchrequest.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchrequest.md index 57e0fbe2c19a9..bba051037e29b 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchrequest.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchrequest.md @@ -7,13 +7,13 @@ Signature: ```typescript -export interface IKibanaSearchRequest +export interface IKibanaSearchRequest ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [debug](./kibana-plugin-plugins-data-public.ikibanasearchrequest.debug.md) | boolean | Optionally tell search strategies to output debug information. | | [id](./kibana-plugin-plugins-data-public.ikibanasearchrequest.id.md) | string | An id can be used to uniquely identify this request. | +| [params](./kibana-plugin-plugins-data-public.ikibanasearchrequest.params.md) | Params | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchrequest.debug.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchrequest.params.md similarity index 61% rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchrequest.debug.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchrequest.params.md index cfb21a78557fd..b7e2006a66c14 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchrequest.debug.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchrequest.params.md @@ -1,13 +1,11 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IKibanaSearchRequest](./kibana-plugin-plugins-data-public.ikibanasearchrequest.md) > [debug](./kibana-plugin-plugins-data-public.ikibanasearchrequest.debug.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IKibanaSearchRequest](./kibana-plugin-plugins-data-public.ikibanasearchrequest.md) > [params](./kibana-plugin-plugins-data-public.ikibanasearchrequest.params.md) -## IKibanaSearchRequest.debug property - -Optionally tell search strategies to output debug information. +## IKibanaSearchRequest.params property Signature: ```typescript -debug?: boolean; +params?: Params; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.ispartial.md similarity index 50% rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.ispartial.md index 00a56c6fe9c31..702c774eb8818 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.ispartial.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-public.iessearchresponse.md) > [isPartial](./kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IKibanaSearchResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.md) > [isPartial](./kibana-plugin-plugins-data-public.ikibanasearchresponse.ispartial.md) -## IEsSearchResponse.isPartial property +## IKibanaSearchResponse.isPartial property Indicates whether the results returned are complete or partial diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.isrunning.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.isrunning.md new file mode 100644 index 0000000000000..1e625ccff26f9 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.isrunning.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IKibanaSearchResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.md) > [isRunning](./kibana-plugin-plugins-data-public.ikibanasearchresponse.isrunning.md) + +## IKibanaSearchResponse.isRunning property + +Indicates whether search is still in flight + +Signature: + +```typescript +isRunning?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.md index f7dfd1ddd2f49..159dc8f4ada18 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.md @@ -7,7 +7,7 @@ Signature: ```typescript -export interface IKibanaSearchResponse +export interface IKibanaSearchResponse ``` ## Properties @@ -15,6 +15,9 @@ export interface IKibanaSearchResponse | Property | Type | Description | | --- | --- | --- | | [id](./kibana-plugin-plugins-data-public.ikibanasearchresponse.id.md) | string | Some responses may contain a unique id to identify the request this response came from. | +| [isPartial](./kibana-plugin-plugins-data-public.ikibanasearchresponse.ispartial.md) | boolean | Indicates whether the results returned are complete or partial | +| [isRunning](./kibana-plugin-plugins-data-public.ikibanasearchresponse.isrunning.md) | boolean | Indicates whether search is still in flight | | [loaded](./kibana-plugin-plugins-data-public.ikibanasearchresponse.loaded.md) | number | If relevant to the search strategy, return a loaded number that represents how progress is indicated. | +| [rawResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md) | RawResponse | | | [total](./kibana-plugin-plugins-data-public.ikibanasearchresponse.total.md) | number | If relevant to the search strategy, return a total number that represents how progress is indicated. | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md new file mode 100644 index 0000000000000..865c7d795801b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IKibanaSearchResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.md) > [rawResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md) + +## IKibanaSearchResponse.rawResponse property + +Signature: + +```typescript +rawResponse: RawResponse; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md index a5bb15c963978..4baf98038f89a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md @@ -9,13 +9,12 @@ Constructs a new instance of the `IndexPattern` class Signature: ```typescript -constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); +constructor({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| id | string | undefined | | -| { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, } | IndexPatternDeps | | +| { spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, } | IndexPatternDeps | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._fetchfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._fetchfields.md deleted file mode 100644 index 8fff8baa71139..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._fetchfields.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [\_fetchFields](./kibana-plugin-plugins-data-public.indexpattern._fetchfields.md) - -## IndexPattern.\_fetchFields() method - -Signature: - -```typescript -_fetchFields(): Promise; -``` -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md index 4bbbd83c65e10..cc3468531fffa 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md @@ -4,10 +4,12 @@ ## IndexPattern.addScriptedField() method +Add scripted field to field list + Signature: ```typescript -addScriptedField(name: string, script: string, fieldType: string | undefined, lang: string): Promise; +addScriptedField(name: string, script: string, fieldType?: string, lang?: string): Promise; ``` ## Parameters @@ -16,7 +18,7 @@ addScriptedField(name: string, script: string, fieldType: string | undefined, la | --- | --- | --- | | name | string | | | script | string | | -| fieldType | string | undefined | | +| fieldType | string | | | lang | string | | Returns: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.create.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.create.md deleted file mode 100644 index 5c122b835f59d..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.create.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [create](./kibana-plugin-plugins-data-public.indexpattern.create.md) - -## IndexPattern.create() method - -Signature: - -```typescript -create(allowOverride?: boolean): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| allowOverride | boolean | | - -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md index b89b244d9826c..904d52fcd5751 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md @@ -7,5 +7,5 @@ Signature: ```typescript -fieldFormatMap: any; +fieldFormatMap: Record; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md index d4dca48c7cd7b..76bc41238526e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md @@ -8,6 +8,6 @@ ```typescript fields: IIndexPatternFieldList & { - toSpec: () => FieldSpec[]; + toSpec: () => IndexPatternFieldMap; }; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md deleted file mode 100644 index 4d44b386a1db1..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [fieldsFetcher](./kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md) - -## IndexPattern.fieldsFetcher property - -Signature: - -```typescript -fieldsFetcher: any; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.flattenhit.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.flattenhit.md index db28d95197bb3..049c3e5e990f7 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.flattenhit.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.flattenhit.md @@ -7,5 +7,5 @@ Signature: ```typescript -flattenHit: any; +flattenHit: (hit: Record, deep?: boolean) => Record; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formatfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formatfield.md index 5a475d6161ac3..aadaddca6cc85 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formatfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formatfield.md @@ -7,5 +7,5 @@ Signature: ```typescript -formatField: any; +formatField: FormatFieldFn; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formathit.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formathit.md index ac515d374a93f..2be76bf1c1e05 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formathit.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formathit.md @@ -7,5 +7,8 @@ Signature: ```typescript -formatHit: any; +formatHit: { + (hit: Record, type?: string): any; + formatField: FormatFieldFn; + }; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.prepbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md similarity index 76% rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.prepbody.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md index 1d77b2a55860e..2c5f30e4889ea 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.prepbody.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md @@ -1,13 +1,15 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [prepBody](./kibana-plugin-plugins-data-public.indexpattern.prepbody.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [getAsSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md) -## IndexPattern.prepBody() method +## IndexPattern.getAsSavedObjectBody() method + +Returns index pattern as saved object body for saving Signature: ```typescript -prepBody(): { +getAsSavedObjectBody(): { title: string; timeFieldName: string | undefined; intervalName: string | undefined; diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md index 7984f7aff1d2d..ba31d60b56892 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md @@ -4,17 +4,19 @@ ## IndexPattern.getFormatterForField() method +Provide a field, get its formatter + Signature: ```typescript -getFormatterForField(field: IndexPatternField | IndexPatternField['spec']): FieldFormat; +getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| field | IndexPatternField | IndexPatternField['spec'] | | +| field | IndexPatternField | IndexPatternField['spec'] | IFieldType | | Returns: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md new file mode 100644 index 0000000000000..349da63c13ca7 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md) + +## IndexPattern.getOriginalSavedObjectBody property + +Get last saved saved object fields + +Signature: + +```typescript +getOriginalSavedObjectBody: () => { + title?: string | undefined; + timeFieldName?: string | undefined; + intervalName?: string | undefined; + fields?: string | undefined; + sourceFilters?: string | undefined; + fieldFormatMap?: string | undefined; + typeMeta?: string | undefined; + type?: string | undefined; + }; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md index 121d32c7c40c8..4ce0144b73882 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md @@ -4,6 +4,8 @@ ## IndexPattern.getSourceFiltering() method +Get the source filtering configuration for that index. + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.init.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.init.md deleted file mode 100644 index 595992dc82b74..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.init.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [init](./kibana-plugin-plugins-data-public.indexpattern.init.md) - -## IndexPattern.init() method - -Signature: - -```typescript -init(): Promise; -``` -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.initfromspec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.initfromspec.md deleted file mode 100644 index 764dd11638221..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.initfromspec.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [initFromSpec](./kibana-plugin-plugins-data-public.indexpattern.initfromspec.md) - -## IndexPattern.initFromSpec() method - -Signature: - -```typescript -initFromSpec(spec: IndexPatternSpec): this; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| spec | IndexPatternSpec | | - -Returns: - -`this` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.iswildcard.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.iswildcard.md deleted file mode 100644 index e5ea55ef1dd48..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.iswildcard.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [isWildcard](./kibana-plugin-plugins-data-public.indexpattern.iswildcard.md) - -## IndexPattern.isWildcard() method - -Signature: - -```typescript -isWildcard(): boolean; -``` -Returns: - -`boolean` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index 87ce1e258712a..2ff575bc4fc22 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -14,22 +14,22 @@ export declare class IndexPattern implements IIndexPattern | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(id, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, })](./kibana-plugin-plugins-data-public.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | +| [(constructor)({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, })](./kibana-plugin-plugins-data-public.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | ## Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md) | | any | | -| [fields](./kibana-plugin-plugins-data-public.indexpattern.fields.md) | | IIndexPatternFieldList & {
toSpec: () => FieldSpec[];
} | | -| [fieldsFetcher](./kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md) | | any | | -| [flattenHit](./kibana-plugin-plugins-data-public.indexpattern.flattenhit.md) | | any | | -| [formatField](./kibana-plugin-plugins-data-public.indexpattern.formatfield.md) | | any | | -| [formatHit](./kibana-plugin-plugins-data-public.indexpattern.formathit.md) | | any | | +| [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md) | | Record<string, any> | | +| [fields](./kibana-plugin-plugins-data-public.indexpattern.fields.md) | | IIndexPatternFieldList & {
toSpec: () => IndexPatternFieldMap;
} | | +| [flattenHit](./kibana-plugin-plugins-data-public.indexpattern.flattenhit.md) | | (hit: Record<string, any>, deep?: boolean) => Record<string, any> | | +| [formatField](./kibana-plugin-plugins-data-public.indexpattern.formatfield.md) | | FormatFieldFn | | +| [formatHit](./kibana-plugin-plugins-data-public.indexpattern.formathit.md) | | {
(hit: Record<string, any>, type?: string): any;
formatField: FormatFieldFn;
} | | +| [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md) | | () => {
title?: string | undefined;
timeFieldName?: string | undefined;
intervalName?: string | undefined;
fields?: string | undefined;
sourceFilters?: string | undefined;
fieldFormatMap?: string | undefined;
typeMeta?: string | undefined;
type?: string | undefined;
} | Get last saved saved object fields | | [id](./kibana-plugin-plugins-data-public.indexpattern.id.md) | | string | | | [intervalName](./kibana-plugin-plugins-data-public.indexpattern.intervalname.md) | | string | undefined | | | [metaFields](./kibana-plugin-plugins-data-public.indexpattern.metafields.md) | | string[] | | -| [originalBody](./kibana-plugin-plugins-data-public.indexpattern.originalbody.md) | | {
[key: string]: any;
} | | +| [resetOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md) | | () => void | Reset last saved saved object fields. used after saving | | [sourceFilters](./kibana-plugin-plugins-data-public.indexpattern.sourcefilters.md) | | SourceFilter[] | | | [timeFieldName](./kibana-plugin-plugins-data-public.indexpattern.timefieldname.md) | | string | undefined | | | [title](./kibana-plugin-plugins-data-public.indexpattern.title.md) | | string | | @@ -41,26 +41,20 @@ export declare class IndexPattern implements IIndexPattern | Method | Modifiers | Description | | --- | --- | --- | -| [\_fetchFields()](./kibana-plugin-plugins-data-public.indexpattern._fetchfields.md) | | | -| [addScriptedField(name, script, fieldType, lang)](./kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md) | | | -| [create(allowOverride)](./kibana-plugin-plugins-data-public.indexpattern.create.md) | | | +| [addScriptedField(name, script, fieldType, lang)](./kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md) | | Add scripted field to field list | | [getAggregationRestrictions()](./kibana-plugin-plugins-data-public.indexpattern.getaggregationrestrictions.md) | | | +| [getAsSavedObjectBody()](./kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md) | | Returns index pattern as saved object body for saving | | [getComputedFields()](./kibana-plugin-plugins-data-public.indexpattern.getcomputedfields.md) | | | | [getFieldByName(name)](./kibana-plugin-plugins-data-public.indexpattern.getfieldbyname.md) | | | -| [getFormatterForField(field)](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md) | | | +| [getFormatterForField(field)](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md) | | Provide a field, get its formatter | | [getNonScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getnonscriptedfields.md) | | | | [getScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getscriptedfields.md) | | | -| [getSourceFiltering()](./kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md) | | | +| [getSourceFiltering()](./kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md) | | Get the source filtering configuration for that index. | | [getTimeField()](./kibana-plugin-plugins-data-public.indexpattern.gettimefield.md) | | | -| [init()](./kibana-plugin-plugins-data-public.indexpattern.init.md) | | | -| [initFromSpec(spec)](./kibana-plugin-plugins-data-public.indexpattern.initfromspec.md) | | | | [isTimeBased()](./kibana-plugin-plugins-data-public.indexpattern.istimebased.md) | | | | [isTimeBasedWildcard()](./kibana-plugin-plugins-data-public.indexpattern.istimebasedwildcard.md) | | | | [isTimeNanosBased()](./kibana-plugin-plugins-data-public.indexpattern.istimenanosbased.md) | | | -| [isWildcard()](./kibana-plugin-plugins-data-public.indexpattern.iswildcard.md) | | | | [popularizeField(fieldName, unit)](./kibana-plugin-plugins-data-public.indexpattern.popularizefield.md) | | | -| [prepBody()](./kibana-plugin-plugins-data-public.indexpattern.prepbody.md) | | | -| [refreshFields()](./kibana-plugin-plugins-data-public.indexpattern.refreshfields.md) | | | -| [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | | +| [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | | [toSpec()](./kibana-plugin-plugins-data-public.indexpattern.tospec.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md deleted file mode 100644 index 4bc3c76afbae9..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [originalBody](./kibana-plugin-plugins-data-public.indexpattern.originalbody.md) - -## IndexPattern.originalBody property - -Signature: - -```typescript -originalBody: { - [key: string]: any; - }; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.refreshfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.refreshfields.md deleted file mode 100644 index 271d0c45a4244..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.refreshfields.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [refreshFields](./kibana-plugin-plugins-data-public.indexpattern.refreshfields.md) - -## IndexPattern.refreshFields() method - -Signature: - -```typescript -refreshFields(): Promise; -``` -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md index e902d9c42b082..aaaebdaccca5d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md @@ -4,6 +4,8 @@ ## IndexPattern.removeScriptedField() method +Remove scripted field from field list + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md new file mode 100644 index 0000000000000..6bbc13d8fd410 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [resetOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md) + +## IndexPattern.resetOriginalSavedObjectBody property + +Reset last saved saved object fields. used after saving + +Signature: + +```typescript +resetOriginalSavedObjectBody: () => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md index eff2349f053ff..77a8ebb0b2d3f 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md @@ -4,12 +4,6 @@ ## IndexPatternAttributes interface -> Warning: This API is now obsolete. -> -> - -Use data plugin interface instead - Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md index 6d62053726197..9b226266f0b5a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md @@ -4,6 +4,8 @@ ## IndexPatternField.conflictDescriptions property +Description of field type conflicts across different indices in the same index pattern + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.count.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.count.md index 84c0a75fd206d..1b8e13a38c6d9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.count.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.count.md @@ -4,6 +4,8 @@ ## IndexPatternField.count property +Count is used for field popularity + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.lang.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.lang.md index 0a8446d40e5ec..b81218eb08886 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.lang.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.lang.md @@ -4,6 +4,8 @@ ## IndexPatternField.lang property +Script field language + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index 215188ffa2607..4f49a9a8fc3ab 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -21,15 +21,15 @@ export declare class IndexPatternField implements IFieldType | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [aggregatable](./kibana-plugin-plugins-data-public.indexpatternfield.aggregatable.md) | | boolean | | -| [conflictDescriptions](./kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md) | | Record<string, string[]> | undefined | | -| [count](./kibana-plugin-plugins-data-public.indexpatternfield.count.md) | | number | | +| [conflictDescriptions](./kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md) | | Record<string, string[]> | undefined | Description of field type conflicts across different indices in the same index pattern | +| [count](./kibana-plugin-plugins-data-public.indexpatternfield.count.md) | | number | Count is used for field popularity | | [displayName](./kibana-plugin-plugins-data-public.indexpatternfield.displayname.md) | | string | | | [esTypes](./kibana-plugin-plugins-data-public.indexpatternfield.estypes.md) | | string[] | undefined | | | [filterable](./kibana-plugin-plugins-data-public.indexpatternfield.filterable.md) | | boolean | | -| [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | undefined | | +| [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | undefined | Script field language | | [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | string | | | [readFromDocValues](./kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md) | | boolean | | -| [script](./kibana-plugin-plugins-data-public.indexpatternfield.script.md) | | string | undefined | | +| [script](./kibana-plugin-plugins-data-public.indexpatternfield.script.md) | | string | undefined | Script field code | | [scripted](./kibana-plugin-plugins-data-public.indexpatternfield.scripted.md) | | boolean | | | [searchable](./kibana-plugin-plugins-data-public.indexpatternfield.searchable.md) | | boolean | | | [sortable](./kibana-plugin-plugins-data-public.indexpatternfield.sortable.md) | | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.script.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.script.md index 27f9c797c92f2..7501e191d9363 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.script.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.script.md @@ -4,6 +4,8 @@ ## IndexPatternField.script property +Script field code + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md index 1d80c90991f55..711d6ad660450 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md @@ -9,24 +9,7 @@ ```typescript toSpec({ getFormatterForField, }?: { getFormatterForField?: IndexPattern['getFormatterForField']; - }): { - count: number; - script: string | undefined; - lang: string | undefined; - conflictDescriptions: Record | undefined; - name: string; - type: string; - esTypes: string[] | undefined; - scripted: boolean; - searchable: boolean; - aggregatable: boolean; - readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; - format: { - id: any; - params: any; - } | undefined; - }; + }): FieldSpec; ``` ## Parameters @@ -37,22 +20,5 @@ toSpec({ getFormatterForField, }?: { Returns: -`{ - count: number; - script: string | undefined; - lang: string | undefined; - conflictDescriptions: Record | undefined; - name: string; - type: string; - esTypes: string[] | undefined; - scripted: boolean; - searchable: boolean; - aggregatable: boolean; - readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; - format: { - id: any; - params: any; - } | undefined; - }` +`FieldSpec` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fields.md new file mode 100644 index 0000000000000..386e080dbe6c2 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fields.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [fields](./kibana-plugin-plugins-data-public.indexpatternspec.fields.md) + +## IndexPatternSpec.fields property + +Signature: + +```typescript +fields?: IndexPatternFieldMap; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.id.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.id.md new file mode 100644 index 0000000000000..55eadbf36c660 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.id.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [id](./kibana-plugin-plugins-data-public.indexpatternspec.id.md) + +## IndexPatternSpec.id property + +Signature: + +```typescript +id?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md new file mode 100644 index 0000000000000..98748661256da --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [intervalName](./kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md) + +## IndexPatternSpec.intervalName property + +Signature: + +```typescript +intervalName?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md new file mode 100644 index 0000000000000..74c4df126e1bf --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) + +## IndexPatternSpec interface + +Signature: + +```typescript +export interface IndexPatternSpec +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [fields](./kibana-plugin-plugins-data-public.indexpatternspec.fields.md) | IndexPatternFieldMap | | +| [id](./kibana-plugin-plugins-data-public.indexpatternspec.id.md) | string | | +| [intervalName](./kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md) | string | | +| [sourceFilters](./kibana-plugin-plugins-data-public.indexpatternspec.sourcefilters.md) | SourceFilter[] | | +| [timeFieldName](./kibana-plugin-plugins-data-public.indexpatternspec.timefieldname.md) | string | | +| [title](./kibana-plugin-plugins-data-public.indexpatternspec.title.md) | string | | +| [type](./kibana-plugin-plugins-data-public.indexpatternspec.type.md) | string | | +| [typeMeta](./kibana-plugin-plugins-data-public.indexpatternspec.typemeta.md) | TypeMeta | | +| [version](./kibana-plugin-plugins-data-public.indexpatternspec.version.md) | string | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.sourcefilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.sourcefilters.md new file mode 100644 index 0000000000000..cda5285730135 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.sourcefilters.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [sourceFilters](./kibana-plugin-plugins-data-public.indexpatternspec.sourcefilters.md) + +## IndexPatternSpec.sourceFilters property + +Signature: + +```typescript +sourceFilters?: SourceFilter[]; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.timefieldname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.timefieldname.md new file mode 100644 index 0000000000000..a527e3ac0658b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.timefieldname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [timeFieldName](./kibana-plugin-plugins-data-public.indexpatternspec.timefieldname.md) + +## IndexPatternSpec.timeFieldName property + +Signature: + +```typescript +timeFieldName?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.title.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.title.md new file mode 100644 index 0000000000000..4cc6d3c2524a7 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.title.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [title](./kibana-plugin-plugins-data-public.indexpatternspec.title.md) + +## IndexPatternSpec.title property + +Signature: + +```typescript +title?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.type.md new file mode 100644 index 0000000000000..d1c49be1b706f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [type](./kibana-plugin-plugins-data-public.indexpatternspec.type.md) + +## IndexPatternSpec.type property + +Signature: + +```typescript +type?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.typemeta.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.typemeta.md new file mode 100644 index 0000000000000..9303047e905d3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.typemeta.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [typeMeta](./kibana-plugin-plugins-data-public.indexpatternspec.typemeta.md) + +## IndexPatternSpec.typeMeta property + +Signature: + +```typescript +typeMeta?: TypeMeta; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.version.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.version.md new file mode 100644 index 0000000000000..43f7cf0226fb0 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.version.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [version](./kibana-plugin-plugins-data-public.indexpatternspec.version.md) + +## IndexPatternSpec.version property + +Signature: + +```typescript +version?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice._constructor_.md new file mode 100644 index 0000000000000..ab397efb1fe0e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice._constructor_.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [(constructor)](./kibana-plugin-plugins-data-public.indexpatternsservice._constructor_.md) + +## IndexPatternsService.(constructor) + +Constructs a new instance of the `IndexPatternsService` class + +Signature: + +```typescript +constructor({ uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, }: IndexPatternsServiceDeps); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, } | IndexPatternsServiceDeps | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.clearcache.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.clearcache.md new file mode 100644 index 0000000000000..b371218325086 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.clearcache.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [clearCache](./kibana-plugin-plugins-data-public.indexpatternsservice.clearcache.md) + +## IndexPatternsService.clearCache property + +Clear index pattern list cache + +Signature: + +```typescript +clearCache: (id?: string | undefined) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.create.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.create.md new file mode 100644 index 0000000000000..d7152ba617cc6 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.create.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [create](./kibana-plugin-plugins-data-public.indexpatternsservice.create.md) + +## IndexPatternsService.create() method + +Create a new index pattern instance + +Signature: + +```typescript +create(spec: IndexPatternSpec, skipFetchFields?: boolean): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| spec | IndexPatternSpec | | +| skipFetchFields | boolean | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md new file mode 100644 index 0000000000000..eebfbb506fb77 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [createAndSave](./kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md) + +## IndexPatternsService.createAndSave() method + +Create a new index pattern and save it right away + +Signature: + +```typescript +createAndSave(spec: IndexPatternSpec, override?: boolean, skipFetchFields?: boolean): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| spec | IndexPatternSpec | | +| override | boolean | | +| skipFetchFields | boolean | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md new file mode 100644 index 0000000000000..8efb33c423b01 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [createSavedObject](./kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md) + +## IndexPatternsService.createSavedObject() method + +Save a new index pattern + +Signature: + +```typescript +createSavedObject(indexPattern: IndexPattern, override?: boolean): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPattern | IndexPattern | | +| override | boolean | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.delete.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.delete.md new file mode 100644 index 0000000000000..aba31ab2c0d29 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.delete.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [delete](./kibana-plugin-plugins-data-public.indexpatternsservice.delete.md) + +## IndexPatternsService.delete() method + +Deletes an index pattern from .kibana index + +Signature: + +```typescript +delete(indexPatternId: string): Promise<{}>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPatternId | string | | + +Returns: + +`Promise<{}>` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.ensuredefaultindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.ensuredefaultindexpattern.md new file mode 100644 index 0000000000000..3b6a8c7e4a04f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.ensuredefaultindexpattern.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [ensureDefaultIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.ensuredefaultindexpattern.md) + +## IndexPatternsService.ensureDefaultIndexPattern property + +Signature: + +```typescript +ensureDefaultIndexPattern: EnsureDefaultIndexPattern; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md new file mode 100644 index 0000000000000..ed365fe03f980 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [fieldArrayToMap](./kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md) + +## IndexPatternsService.fieldArrayToMap property + +Converts field array to map + +Signature: + +```typescript +fieldArrayToMap: (fields: FieldSpec[]) => Record; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.get.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.get.md new file mode 100644 index 0000000000000..4aad6df6b413b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.get.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [get](./kibana-plugin-plugins-data-public.indexpatternsservice.get.md) + +## IndexPatternsService.get property + +Get an index pattern by id. Cache optimized + +Signature: + +```typescript +get: (id: string) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md new file mode 100644 index 0000000000000..ad2a167bd8c74 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getCache](./kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md) + +## IndexPatternsService.getCache property + +Signature: + +```typescript +getCache: () => Promise[] | null | undefined>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md new file mode 100644 index 0000000000000..01d4efeffe921 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getDefault](./kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md) + +## IndexPatternsService.getDefault property + +Get default index pattern + +Signature: + +```typescript +getDefault: () => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md new file mode 100644 index 0000000000000..c06c3c6f68492 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getFieldsForIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md) + +## IndexPatternsService.getFieldsForIndexPattern property + +Get field list by providing an index patttern (or spec) + +Signature: + +```typescript +getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md new file mode 100644 index 0000000000000..aec84866b9e58 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getFieldsForWildcard](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md) + +## IndexPatternsService.getFieldsForWildcard property + +Get field list by providing { pattern } + +Signature: + +```typescript +getFieldsForWildcard: (options?: GetFieldsOptions) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getids.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getids.md new file mode 100644 index 0000000000000..a012e0dc9d9c5 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getids.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getIds](./kibana-plugin-plugins-data-public.indexpatternsservice.getids.md) + +## IndexPatternsService.getIds property + +Get list of index pattern ids + +Signature: + +```typescript +getIds: (refresh?: boolean) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md new file mode 100644 index 0000000000000..7d29ced66afa8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getIdsWithTitle](./kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md) + +## IndexPatternsService.getIdsWithTitle property + +Get list of index pattern ids with titles + +Signature: + +```typescript +getIdsWithTitle: (refresh?: boolean) => Promise>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md new file mode 100644 index 0000000000000..04cc294a79dfc --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getTitles](./kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md) + +## IndexPatternsService.getTitles property + +Get list of index pattern titles + +Signature: + +```typescript +getTitles: (refresh?: boolean) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md new file mode 100644 index 0000000000000..af087344268d7 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md @@ -0,0 +1,47 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) + +## IndexPatternsService class + +Signature: + +```typescript +export declare class IndexPatternsService +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)({ uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, })](./kibana-plugin-plugins-data-public.indexpatternsservice._constructor_.md) | | Constructs a new instance of the IndexPatternsService class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [clearCache](./kibana-plugin-plugins-data-public.indexpatternsservice.clearcache.md) | | (id?: string | undefined) => void | Clear index pattern list cache | +| [ensureDefaultIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.ensuredefaultindexpattern.md) | | EnsureDefaultIndexPattern | | +| [fieldArrayToMap](./kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md) | | (fields: FieldSpec[]) => Record<string, FieldSpec> | Converts field array to map | +| [get](./kibana-plugin-plugins-data-public.indexpatternsservice.get.md) | | (id: string) => Promise<IndexPattern> | Get an index pattern by id. Cache optimized | +| [getCache](./kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md) | | () => Promise<SavedObject<IndexPatternSavedObjectAttrs>[] | null | undefined> | | +| [getDefault](./kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md) | | () => Promise<IndexPattern | null> | Get default index pattern | +| [getFieldsForIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md) | | (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions) => Promise<any> | Get field list by providing an index patttern (or spec) | +| [getFieldsForWildcard](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md) | | (options?: GetFieldsOptions) => Promise<any> | Get field list by providing { pattern } | +| [getIds](./kibana-plugin-plugins-data-public.indexpatternsservice.getids.md) | | (refresh?: boolean) => Promise<string[]> | Get list of index pattern ids | +| [getIdsWithTitle](./kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md) | | (refresh?: boolean) => Promise<Array<{
id: string;
title: string;
}>> | Get list of index pattern ids with titles | +| [getTitles](./kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md) | | (refresh?: boolean) => Promise<string[]> | Get list of index pattern titles | +| [refreshFields](./kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md) | | (indexPattern: IndexPattern) => Promise<void> | Refresh field list for a given index pattern | +| [savedObjectToSpec](./kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md) | | (savedObject: SavedObject<IndexPatternAttributes>) => IndexPatternSpec | Converts index pattern saved object to index pattern spec | +| [setDefault](./kibana-plugin-plugins-data-public.indexpatternsservice.setdefault.md) | | (id: string, force?: boolean) => Promise<void> | Optionally set default index pattern, unless force = true | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [create(spec, skipFetchFields)](./kibana-plugin-plugins-data-public.indexpatternsservice.create.md) | | Create a new index pattern instance | +| [createAndSave(spec, override, skipFetchFields)](./kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md) | | Create a new index pattern and save it right away | +| [createSavedObject(indexPattern, override)](./kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md) | | Save a new index pattern | +| [delete(indexPatternId)](./kibana-plugin-plugins-data-public.indexpatternsservice.delete.md) | | Deletes an index pattern from .kibana index | +| [updateSavedObject(indexPattern, saveAttempts)](./kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md) | | Save existing index pattern. Will attempt to merge differences if there are conflicts | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md new file mode 100644 index 0000000000000..b7c47efbb445a --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [refreshFields](./kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md) + +## IndexPatternsService.refreshFields property + +Refresh field list for a given index pattern + +Signature: + +```typescript +refreshFields: (indexPattern: IndexPattern) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md new file mode 100644 index 0000000000000..7bd40c9cafd42 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [savedObjectToSpec](./kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md) + +## IndexPatternsService.savedObjectToSpec property + +Converts index pattern saved object to index pattern spec + +Signature: + +```typescript +savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.setdefault.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.setdefault.md new file mode 100644 index 0000000000000..2bf8eaa03d1ae --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.setdefault.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [setDefault](./kibana-plugin-plugins-data-public.indexpatternsservice.setdefault.md) + +## IndexPatternsService.setDefault property + +Optionally set default index pattern, unless force = true + +Signature: + +```typescript +setDefault: (id: string, force?: boolean) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md new file mode 100644 index 0000000000000..3973f5d4c3e7b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [updateSavedObject](./kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md) + +## IndexPatternsService.updateSavedObject() method + +Save existing index pattern. Will attempt to merge differences if there are conflicts + +Signature: + +```typescript +updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPattern | IndexPattern | | +| saveAttempts | number | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iscompleteresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iscompleteresponse.md new file mode 100644 index 0000000000000..e17e453ecb749 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iscompleteresponse.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [isCompleteResponse](./kibana-plugin-plugins-data-public.iscompleteresponse.md) + +## isCompleteResponse variable + +Signature: + +```typescript +isCompleteResponse: (response?: IKibanaSearchResponse | undefined) => boolean | undefined +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md index 861b59e73ef04..025ca6681d39b 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type ISearchGeneric = (request: SearchStrategyRequest, options?: ISearchOptions) => Observable; +export declare type ISearchGeneric = (request: SearchStrategyRequest, options?: ISearchOptions) => Observable; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iserrorresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iserrorresponse.md new file mode 100644 index 0000000000000..e4ac35f19e959 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iserrorresponse.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [isErrorResponse](./kibana-plugin-plugins-data-public.iserrorresponse.md) + +## isErrorResponse variable + +Signature: + +```typescript +isErrorResponse: (response?: IKibanaSearchResponse | undefined) => boolean | undefined +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ispartialresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ispartialresponse.md new file mode 100644 index 0000000000000..4b707ceeacc89 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ispartialresponse.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [isPartialResponse](./kibana-plugin-plugins-data-public.ispartialresponse.md) + +## isPartialResponse variable + +Signature: + +```typescript +isPartialResponse: (response?: IKibanaSearchResponse | undefined) => boolean | undefined +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index f51549c81fb62..0f45b5a727676 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -11,11 +11,13 @@ | [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) | | | [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) | | | [AggParamType](./kibana-plugin-plugins-data-public.aggparamtype.md) | | +| [DuplicateIndexPatternError](./kibana-plugin-plugins-data-public.duplicateindexpatternerror.md) | | | [FieldFormat](./kibana-plugin-plugins-data-public.fieldformat.md) | | | [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) | | | [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) | | | [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) | | | [IndexPatternSelect](./kibana-plugin-plugins-data-public.indexpatternselect.md) | | +| [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) | | | [OptionedParamType](./kibana-plugin-plugins-data-public.optionedparamtype.md) | | | [Plugin](./kibana-plugin-plugins-data-public.plugin.md) | | | [RequestTimeoutError](./kibana-plugin-plugins-data-public.requesttimeouterror.md) | Class used to signify that a request timed out. Useful for applications to conditionally handle this type of error differently than other errors. | @@ -57,17 +59,16 @@ | [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-public.fieldformatconfig.md) | | | [FieldMappingSpec](./kibana-plugin-plugins-data-public.fieldmappingspec.md) | | -| [Filter](./kibana-plugin-plugins-data-public.filter.md) | | | [IDataPluginServices](./kibana-plugin-plugins-data-public.idatapluginservices.md) | | | [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.md) | | -| [IEsSearchResponse](./kibana-plugin-plugins-data-public.iessearchresponse.md) | | | [IFieldSubType](./kibana-plugin-plugins-data-public.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) | | | [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) | | | [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) | | | [IKibanaSearchRequest](./kibana-plugin-plugins-data-public.ikibanasearchrequest.md) | | | [IKibanaSearchResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.md) | | -| [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) | Use data plugin interface instead | +| [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) | | +| [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) | | | [IndexPatternTypeMeta](./kibana-plugin-plugins-data-public.indexpatterntypemeta.md) | | | [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) | | | [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) | The setup contract exposed by the Search plugin exposes the search strategy extension point. | @@ -75,9 +76,9 @@ | [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | high level search service | | [KueryNode](./kibana-plugin-plugins-data-public.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-public.optionedvalueprop.md) | | -| [Query](./kibana-plugin-plugins-data-public.query.md) | | | [QueryState](./kibana-plugin-plugins-data-public.querystate.md) | All query state service state | | [QueryStateChange](./kibana-plugin-plugins-data-public.querystatechange.md) | | +| [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) | | | [QuerySuggestionBasic](./kibana-plugin-plugins-data-public.querysuggestionbasic.md) | \* | | [QuerySuggestionField](./kibana-plugin-plugins-data-public.querysuggestionfield.md) | \* | | [QuerySuggestionGetFnArgs](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md) | \* | @@ -90,7 +91,6 @@ | [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | search source fields | | [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) | \* | | [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) | \* | -| [TimeRange](./kibana-plugin-plugins-data-public.timerange.md) | | ## Variables @@ -115,8 +115,11 @@ | [getKbnTypeNames](./kibana-plugin-plugins-data-public.getkbntypenames.md) | Get the esTypes known by all kbnFieldTypes {Array} | | [indexPatterns](./kibana-plugin-plugins-data-public.indexpatterns.md) | | | [injectSearchSourceReferences](./kibana-plugin-plugins-data-public.injectsearchsourcereferences.md) | | +| [isCompleteResponse](./kibana-plugin-plugins-data-public.iscompleteresponse.md) | | +| [isErrorResponse](./kibana-plugin-plugins-data-public.iserrorresponse.md) | | | [isFilter](./kibana-plugin-plugins-data-public.isfilter.md) | | | [isFilters](./kibana-plugin-plugins-data-public.isfilters.md) | | +| [isPartialResponse](./kibana-plugin-plugins-data-public.ispartialresponse.md) | | | [isQuery](./kibana-plugin-plugins-data-public.isquery.md) | | | [isTimeRange](./kibana-plugin-plugins-data-public.istimerange.md) | | | [parseSearchSourceJSON](./kibana-plugin-plugins-data-public.parsesearchsourcejson.md) | | @@ -145,8 +148,10 @@ | [FieldFormatsContentType](./kibana-plugin-plugins-data-public.fieldformatscontenttype.md) | \* | | [FieldFormatsGetConfigFn](./kibana-plugin-plugins-data-public.fieldformatsgetconfigfn.md) | | | [FieldFormatsStart](./kibana-plugin-plugins-data-public.fieldformatsstart.md) | | +| [Filter](./kibana-plugin-plugins-data-public.filter.md) | | | [IAggConfig](./kibana-plugin-plugins-data-public.iaggconfig.md) | AggConfig This class represents an aggregation, which is displayed in the left-hand nav of the Visualize app. | | [IAggType](./kibana-plugin-plugins-data-public.iaggtype.md) | | +| [IEsSearchResponse](./kibana-plugin-plugins-data-public.iessearchresponse.md) | | | [IFieldFormat](./kibana-plugin-plugins-data-public.ifieldformat.md) | | | [IFieldFormatsRegistry](./kibana-plugin-plugins-data-public.ifieldformatsregistry.md) | | | [IFieldParamType](./kibana-plugin-plugins-data-public.ifieldparamtype.md) | | @@ -162,6 +167,7 @@ | [ParsedInterval](./kibana-plugin-plugins-data-public.parsedinterval.md) | | | [PhraseFilter](./kibana-plugin-plugins-data-public.phrasefilter.md) | | | [PhrasesFilter](./kibana-plugin-plugins-data-public.phrasesfilter.md) | | +| [Query](./kibana-plugin-plugins-data-public.query.md) | | | [QueryStart](./kibana-plugin-plugins-data-public.querystart.md) | | | [QuerySuggestion](./kibana-plugin-plugins-data-public.querysuggestion.md) | \* | | [QuerySuggestionGetFn](./kibana-plugin-plugins-data-public.querysuggestiongetfn.md) | | @@ -173,4 +179,5 @@ | [TabbedAggRow](./kibana-plugin-plugins-data-public.tabbedaggrow.md) | \* | | [TimefilterContract](./kibana-plugin-plugins-data-public.timefiltercontract.md) | | | [TimeHistoryContract](./kibana-plugin-plugins-data-public.timehistorycontract.md) | | +| [TimeRange](./kibana-plugin-plugins-data-public.timerange.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md index e85747b8cc3d7..aa7c3bb5d4932 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md @@ -7,5 +7,5 @@ Signature: ```typescript -QueryStringInput: React.FC> +QueryStringInput: React.FC ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md new file mode 100644 index 0000000000000..5a41852001ac0 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [bubbleSubmitEvent](./kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md) + +## QueryStringInputProps.bubbleSubmitEvent property + +Signature: + +```typescript +bubbleSubmitEvent?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.classname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.classname.md new file mode 100644 index 0000000000000..7fa3b76977183 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.classname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [className](./kibana-plugin-plugins-data-public.querystringinputprops.classname.md) + +## QueryStringInputProps.className property + +Signature: + +```typescript +className?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md new file mode 100644 index 0000000000000..edaedf49f4b10 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [dataTestSubj](./kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md) + +## QueryStringInputProps.dataTestSubj property + +Signature: + +```typescript +dataTestSubj?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md new file mode 100644 index 0000000000000..cc4c6f606409e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [disableAutoFocus](./kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md) + +## QueryStringInputProps.disableAutoFocus property + +Signature: + +```typescript +disableAutoFocus?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md new file mode 100644 index 0000000000000..3783138696020 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [indexPatterns](./kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md) + +## QueryStringInputProps.indexPatterns property + +Signature: + +```typescript +indexPatterns: Array; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md new file mode 100644 index 0000000000000..a282ac3bc5049 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [isInvalid](./kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md) + +## QueryStringInputProps.isInvalid property + +Signature: + +```typescript +isInvalid?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md new file mode 100644 index 0000000000000..d133a0930b53d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [languageSwitcherPopoverAnchorPosition](./kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md) + +## QueryStringInputProps.languageSwitcherPopoverAnchorPosition property + +Signature: + +```typescript +languageSwitcherPopoverAnchorPosition?: PopoverAnchorPosition; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.md new file mode 100644 index 0000000000000..d503980da7947 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.md @@ -0,0 +1,34 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) + +## QueryStringInputProps interface + +Signature: + +```typescript +export interface QueryStringInputProps +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [bubbleSubmitEvent](./kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md) | boolean | | +| [className](./kibana-plugin-plugins-data-public.querystringinputprops.classname.md) | string | | +| [dataTestSubj](./kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md) | string | | +| [disableAutoFocus](./kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md) | boolean | | +| [indexPatterns](./kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md) | Array<IIndexPattern | string> | | +| [isInvalid](./kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md) | boolean | | +| [languageSwitcherPopoverAnchorPosition](./kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md) | PopoverAnchorPosition | | +| [onBlur](./kibana-plugin-plugins-data-public.querystringinputprops.onblur.md) | () => void | | +| [onChange](./kibana-plugin-plugins-data-public.querystringinputprops.onchange.md) | (query: Query) => void | | +| [onChangeQueryInputFocus](./kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md) | (isFocused: boolean) => void | | +| [onSubmit](./kibana-plugin-plugins-data-public.querystringinputprops.onsubmit.md) | (query: Query) => void | | +| [persistedLog](./kibana-plugin-plugins-data-public.querystringinputprops.persistedlog.md) | PersistedLog | | +| [placeholder](./kibana-plugin-plugins-data-public.querystringinputprops.placeholder.md) | string | | +| [prepend](./kibana-plugin-plugins-data-public.querystringinputprops.prepend.md) | any | | +| [query](./kibana-plugin-plugins-data-public.querystringinputprops.query.md) | Query | | +| [screenTitle](./kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md) | string | | +| [size](./kibana-plugin-plugins-data-public.querystringinputprops.size.md) | SuggestionsListSize | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onblur.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onblur.md new file mode 100644 index 0000000000000..10f2ae2ea4f14 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onblur.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [onBlur](./kibana-plugin-plugins-data-public.querystringinputprops.onblur.md) + +## QueryStringInputProps.onBlur property + +Signature: + +```typescript +onBlur?: () => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchange.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchange.md new file mode 100644 index 0000000000000..fee44d7afd506 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchange.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [onChange](./kibana-plugin-plugins-data-public.querystringinputprops.onchange.md) + +## QueryStringInputProps.onChange property + +Signature: + +```typescript +onChange?: (query: Query) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md new file mode 100644 index 0000000000000..0421ae9c8bac5 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [onChangeQueryInputFocus](./kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md) + +## QueryStringInputProps.onChangeQueryInputFocus property + +Signature: + +```typescript +onChangeQueryInputFocus?: (isFocused: boolean) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onsubmit.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onsubmit.md new file mode 100644 index 0000000000000..951ec7419485f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onsubmit.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [onSubmit](./kibana-plugin-plugins-data-public.querystringinputprops.onsubmit.md) + +## QueryStringInputProps.onSubmit property + +Signature: + +```typescript +onSubmit?: (query: Query) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.persistedlog.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.persistedlog.md new file mode 100644 index 0000000000000..d1a8efb364016 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.persistedlog.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [persistedLog](./kibana-plugin-plugins-data-public.querystringinputprops.persistedlog.md) + +## QueryStringInputProps.persistedLog property + +Signature: + +```typescript +persistedLog?: PersistedLog; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.placeholder.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.placeholder.md new file mode 100644 index 0000000000000..31e41f4d55205 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.placeholder.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [placeholder](./kibana-plugin-plugins-data-public.querystringinputprops.placeholder.md) + +## QueryStringInputProps.placeholder property + +Signature: + +```typescript +placeholder?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.prepend.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.prepend.md new file mode 100644 index 0000000000000..7be882058d3fd --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.prepend.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [prepend](./kibana-plugin-plugins-data-public.querystringinputprops.prepend.md) + +## QueryStringInputProps.prepend property + +Signature: + +```typescript +prepend?: any; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.query.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.query.md new file mode 100644 index 0000000000000..f15f6d082332b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.query.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [query](./kibana-plugin-plugins-data-public.querystringinputprops.query.md) + +## QueryStringInputProps.query property + +Signature: + +```typescript +query: Query; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md new file mode 100644 index 0000000000000..0c80252d74571 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [screenTitle](./kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md) + +## QueryStringInputProps.screenTitle property + +Signature: + +```typescript +screenTitle?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.size.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.size.md new file mode 100644 index 0000000000000..6b0e53a23e07b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.size.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [size](./kibana-plugin-plugins-data-public.querystringinputprops.size.md) + +## QueryStringInputProps.size property + +Signature: + +```typescript +size?: SuggestionsListSize; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md index 1752d183a8737..1a71b5808f485 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md @@ -9,7 +9,7 @@ Searches using the given `search` method. Overrides the `AbortSignal` with one t Signature: ```typescript -search(request: IEsSearchRequest, options?: ISearchOptions): Observable; +search(request: IEsSearchRequest, options?: ISearchOptions): Observable; ``` ## Parameters @@ -21,5 +21,5 @@ search(request: IEsSearchRequest, options?: ISearchOptions): ObservableReturns: -`Observable` +`Observable` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.aggregatable.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.aggregatable.md new file mode 100644 index 0000000000000..2889ee34ad77b --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.aggregatable.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [aggregatable](./kibana-plugin-plugins-data-server.fielddescriptor.aggregatable.md) + +## FieldDescriptor.aggregatable property + +Signature: + +```typescript +aggregatable: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.estypes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.estypes.md new file mode 100644 index 0000000000000..9caa374d8da48 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.estypes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [esTypes](./kibana-plugin-plugins-data-server.fielddescriptor.estypes.md) + +## FieldDescriptor.esTypes property + +Signature: + +```typescript +esTypes: string[]; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.md new file mode 100644 index 0000000000000..693de675da940 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) + +## FieldDescriptor interface + +Signature: + +```typescript +export interface FieldDescriptor +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [aggregatable](./kibana-plugin-plugins-data-server.fielddescriptor.aggregatable.md) | boolean | | +| [esTypes](./kibana-plugin-plugins-data-server.fielddescriptor.estypes.md) | string[] | | +| [name](./kibana-plugin-plugins-data-server.fielddescriptor.name.md) | string | | +| [readFromDocValues](./kibana-plugin-plugins-data-server.fielddescriptor.readfromdocvalues.md) | boolean | | +| [searchable](./kibana-plugin-plugins-data-server.fielddescriptor.searchable.md) | boolean | | +| [subType](./kibana-plugin-plugins-data-server.fielddescriptor.subtype.md) | FieldSubType | | +| [type](./kibana-plugin-plugins-data-server.fielddescriptor.type.md) | string | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.name.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.name.md new file mode 100644 index 0000000000000..178880a34cd4d --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.name.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [name](./kibana-plugin-plugins-data-server.fielddescriptor.name.md) + +## FieldDescriptor.name property + +Signature: + +```typescript +name: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.readfromdocvalues.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.readfromdocvalues.md new file mode 100644 index 0000000000000..b60dc5d0dfed0 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.readfromdocvalues.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [readFromDocValues](./kibana-plugin-plugins-data-server.fielddescriptor.readfromdocvalues.md) + +## FieldDescriptor.readFromDocValues property + +Signature: + +```typescript +readFromDocValues: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.searchable.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.searchable.md new file mode 100644 index 0000000000000..efc7b4219a355 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.searchable.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [searchable](./kibana-plugin-plugins-data-server.fielddescriptor.searchable.md) + +## FieldDescriptor.searchable property + +Signature: + +```typescript +searchable: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.subtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.subtype.md new file mode 100644 index 0000000000000..b08179f12f250 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.subtype.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [subType](./kibana-plugin-plugins-data-server.fielddescriptor.subtype.md) + +## FieldDescriptor.subType property + +Signature: + +```typescript +subType?: FieldSubType; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.type.md new file mode 100644 index 0000000000000..7b0513a60c90e --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [type](./kibana-plugin-plugins-data-server.fielddescriptor.type.md) + +## FieldDescriptor.type property + +Signature: + +```typescript +type: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md index 0dfa23eb64c1b..9141bcdd2e8d7 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md @@ -7,7 +7,7 @@ Signature: ```typescript -export interface IEsSearchRequest extends IKibanaSearchRequest +export interface IEsSearchRequest extends IKibanaSearchRequest ``` ## Properties @@ -15,5 +15,4 @@ export interface IEsSearchRequest extends IKibanaSearchRequest | Property | Type | Description | | --- | --- | --- | | [indexType](./kibana-plugin-plugins-data-server.iessearchrequest.indextype.md) | string | | -| [params](./kibana-plugin-plugins-data-server.iessearchrequest.params.md) | ISearchRequestParams | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.params.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.params.md deleted file mode 100644 index d65281973c951..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.params.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) > [params](./kibana-plugin-plugins-data-server.iessearchrequest.params.md) - -## IEsSearchRequest.params property - -Signature: - -```typescript -params?: ISearchRequestParams; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md deleted file mode 100644 index fbddfc1cd9fc4..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) > [isPartial](./kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md) - -## IEsSearchResponse.isPartial property - -Indicates whether the results returned are complete or partial - -Signature: - -```typescript -isPartial?: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md deleted file mode 100644 index 01f3982957d5c..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) > [isRunning](./kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md) - -## IEsSearchResponse.isRunning property - -Indicates whether async search is still in flight - -Signature: - -```typescript -isRunning?: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md index 55c0399e90e2f..d333af1b278c2 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md @@ -2,19 +2,10 @@ [Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) -## IEsSearchResponse interface +## IEsSearchResponse type Signature: ```typescript -export interface IEsSearchResponse extends IKibanaSearchResponse +export declare type IEsSearchResponse = IKibanaSearchResponse>; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [isPartial](./kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md) | boolean | Indicates whether the results returned are complete or partial | -| [isRunning](./kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md) | boolean | Indicates whether async search is still in flight | -| [rawResponse](./kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md) | SearchResponse<Source> | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md deleted file mode 100644 index 9987debfa551c..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) > [rawResponse](./kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md) - -## IEsSearchResponse.rawResponse property - -Signature: - -```typescript -rawResponse: SearchResponse; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fieldformatmap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fieldformatmap.md deleted file mode 100644 index ab9e3171d7d7b..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fieldformatmap.md +++ /dev/null @@ -1,14 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [fieldFormatMap](./kibana-plugin-plugins-data-server.iindexpattern.fieldformatmap.md) - -## IIndexPattern.fieldFormatMap property - -Signature: - -```typescript -fieldFormatMap?: Record; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fields.md deleted file mode 100644 index fb6d046ff2174..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fields.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [fields](./kibana-plugin-plugins-data-server.iindexpattern.fields.md) - -## IIndexPattern.fields property - -Signature: - -```typescript -fields: IFieldType[]; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md deleted file mode 100644 index a4d6abcf86a94..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [getTimeField](./kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md) - -## IIndexPattern.getTimeField() method - -Signature: - -```typescript -getTimeField?(): IFieldType | undefined; -``` -Returns: - -`IFieldType | undefined` - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.id.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.id.md deleted file mode 100644 index cac263df0f9aa..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [id](./kibana-plugin-plugins-data-server.iindexpattern.id.md) - -## IIndexPattern.id property - -Signature: - -```typescript -id?: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.md deleted file mode 100644 index a79244a24acf5..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) - -## IIndexPattern interface - -Signature: - -```typescript -export interface IIndexPattern -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [fieldFormatMap](./kibana-plugin-plugins-data-server.iindexpattern.fieldformatmap.md) | Record<string, {
id: string;
params: unknown;
}> | | -| [fields](./kibana-plugin-plugins-data-server.iindexpattern.fields.md) | IFieldType[] | | -| [id](./kibana-plugin-plugins-data-server.iindexpattern.id.md) | string | | -| [timeFieldName](./kibana-plugin-plugins-data-server.iindexpattern.timefieldname.md) | string | | -| [title](./kibana-plugin-plugins-data-server.iindexpattern.title.md) | string | | -| [type](./kibana-plugin-plugins-data-server.iindexpattern.type.md) | string | | - -## Methods - -| Method | Description | -| --- | --- | -| [getTimeField()](./kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md) | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.timefieldname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.timefieldname.md deleted file mode 100644 index 14cf514477da4..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.timefieldname.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [timeFieldName](./kibana-plugin-plugins-data-server.iindexpattern.timefieldname.md) - -## IIndexPattern.timeFieldName property - -Signature: - -```typescript -timeFieldName?: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.title.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.title.md deleted file mode 100644 index 119963d7ff95d..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.title.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [title](./kibana-plugin-plugins-data-server.iindexpattern.title.md) - -## IIndexPattern.title property - -Signature: - -```typescript -title: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.type.md deleted file mode 100644 index 6b89b71664b23..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [type](./kibana-plugin-plugins-data-server.iindexpattern.type.md) - -## IIndexPattern.type property - -Signature: - -```typescript -type?: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern._constructor_.md new file mode 100644 index 0000000000000..f7f8e51c4b632 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern._constructor_.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [(constructor)](./kibana-plugin-plugins-data-server.indexpattern._constructor_.md) + +## IndexPattern.(constructor) + +Constructs a new instance of the `IndexPattern` class + +Signature: + +```typescript +constructor({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, } | IndexPatternDeps | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md new file mode 100644 index 0000000000000..6d206e88b5b56 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [addScriptedField](./kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md) + +## IndexPattern.addScriptedField() method + +Add scripted field to field list + +Signature: + +```typescript +addScriptedField(name: string, script: string, fieldType?: string, lang?: string): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | +| script | string | | +| fieldType | string | | +| lang | string | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md new file mode 100644 index 0000000000000..2f686bd313d58 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md) + +## IndexPattern.fieldFormatMap property + +Signature: + +```typescript +fieldFormatMap: Record; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fields.md new file mode 100644 index 0000000000000..5b22014486c02 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fields.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [fields](./kibana-plugin-plugins-data-server.indexpattern.fields.md) + +## IndexPattern.fields property + +Signature: + +```typescript +fields: IIndexPatternFieldList & { + toSpec: () => IndexPatternFieldMap; + }; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.flattenhit.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.flattenhit.md new file mode 100644 index 0000000000000..33c6dedc6dcd8 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.flattenhit.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [flattenHit](./kibana-plugin-plugins-data-server.indexpattern.flattenhit.md) + +## IndexPattern.flattenHit property + +Signature: + +```typescript +flattenHit: (hit: Record, deep?: boolean) => Record; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formatfield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formatfield.md new file mode 100644 index 0000000000000..07db8a0805b07 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formatfield.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [formatField](./kibana-plugin-plugins-data-server.indexpattern.formatfield.md) + +## IndexPattern.formatField property + +Signature: + +```typescript +formatField: FormatFieldFn; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formathit.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formathit.md new file mode 100644 index 0000000000000..75f282a8991fc --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formathit.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [formatHit](./kibana-plugin-plugins-data-server.indexpattern.formathit.md) + +## IndexPattern.formatHit property + +Signature: + +```typescript +formatHit: { + (hit: Record, type?: string): any; + formatField: FormatFieldFn; + }; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md new file mode 100644 index 0000000000000..b655e779e4fa4 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md @@ -0,0 +1,29 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getAggregationRestrictions](./kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md) + +## IndexPattern.getAggregationRestrictions() method + +Signature: + +```typescript +getAggregationRestrictions(): Record> | undefined; +``` +Returns: + +`Record> | undefined` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md new file mode 100644 index 0000000000000..f1bdb2f729414 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md @@ -0,0 +1,35 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getAsSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md) + +## IndexPattern.getAsSavedObjectBody() method + +Returns index pattern as saved object body for saving + +Signature: + +```typescript +getAsSavedObjectBody(): { + title: string; + timeFieldName: string | undefined; + intervalName: string | undefined; + sourceFilters: string | undefined; + fields: string | undefined; + fieldFormatMap: string | undefined; + type: string | undefined; + typeMeta: string | undefined; + }; +``` +Returns: + +`{ + title: string; + timeFieldName: string | undefined; + intervalName: string | undefined; + sourceFilters: string | undefined; + fields: string | undefined; + fieldFormatMap: string | undefined; + type: string | undefined; + typeMeta: string | undefined; + }` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md new file mode 100644 index 0000000000000..eab6ae9bf9033 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md @@ -0,0 +1,29 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getComputedFields](./kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md) + +## IndexPattern.getComputedFields() method + +Signature: + +```typescript +getComputedFields(): { + storedFields: string[]; + scriptFields: any; + docvalueFields: { + field: any; + format: string; + }[]; + }; +``` +Returns: + +`{ + storedFields: string[]; + scriptFields: any; + docvalueFields: { + field: any; + format: string; + }[]; + }` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md new file mode 100644 index 0000000000000..712be3b72828a --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getFieldByName](./kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md) + +## IndexPattern.getFieldByName() method + +Signature: + +```typescript +getFieldByName(name: string): IndexPatternField | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`IndexPatternField | undefined` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md new file mode 100644 index 0000000000000..7dc2756009f4e --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getFormatterForField](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md) + +## IndexPattern.getFormatterForField() method + +Provide a field, get its formatter + +Signature: + +```typescript +getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | IndexPatternField | IndexPatternField['spec'] | IFieldType | | + +Returns: + +`FieldFormat` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md new file mode 100644 index 0000000000000..89d79d9b750fa --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getNonScriptedFields](./kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md) + +## IndexPattern.getNonScriptedFields() method + +Signature: + +```typescript +getNonScriptedFields(): IndexPatternField[]; +``` +Returns: + +`IndexPatternField[]` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md new file mode 100644 index 0000000000000..324f9d0152ab5 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md) + +## IndexPattern.getOriginalSavedObjectBody property + +Get last saved saved object fields + +Signature: + +```typescript +getOriginalSavedObjectBody: () => { + title?: string | undefined; + timeFieldName?: string | undefined; + intervalName?: string | undefined; + fields?: string | undefined; + sourceFilters?: string | undefined; + fieldFormatMap?: string | undefined; + typeMeta?: string | undefined; + type?: string | undefined; + }; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md new file mode 100644 index 0000000000000..edfff8ec5efac --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getScriptedFields](./kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md) + +## IndexPattern.getScriptedFields() method + +Signature: + +```typescript +getScriptedFields(): IndexPatternField[]; +``` +Returns: + +`IndexPatternField[]` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md new file mode 100644 index 0000000000000..240f9b4fb0aa2 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getSourceFiltering](./kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md) + +## IndexPattern.getSourceFiltering() method + +Get the source filtering configuration for that index. + +Signature: + +```typescript +getSourceFiltering(): { + excludes: any[]; + }; +``` +Returns: + +`{ + excludes: any[]; + }` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.gettimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.gettimefield.md new file mode 100644 index 0000000000000..b5806f883fb9f --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.gettimefield.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getTimeField](./kibana-plugin-plugins-data-server.indexpattern.gettimefield.md) + +## IndexPattern.getTimeField() method + +Signature: + +```typescript +getTimeField(): IndexPatternField | undefined; +``` +Returns: + +`IndexPatternField | undefined` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.id.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.id.md new file mode 100644 index 0000000000000..8fad82bd06705 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.id.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [id](./kibana-plugin-plugins-data-server.indexpattern.id.md) + +## IndexPattern.id property + +Signature: + +```typescript +id?: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.intervalname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.intervalname.md new file mode 100644 index 0000000000000..caaa6929235f8 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.intervalname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [intervalName](./kibana-plugin-plugins-data-server.indexpattern.intervalname.md) + +## IndexPattern.intervalName property + +Signature: + +```typescript +intervalName: string | undefined; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebased.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebased.md new file mode 100644 index 0000000000000..790744979942d --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebased.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [isTimeBased](./kibana-plugin-plugins-data-server.indexpattern.istimebased.md) + +## IndexPattern.isTimeBased() method + +Signature: + +```typescript +isTimeBased(): boolean; +``` +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebasedwildcard.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebasedwildcard.md new file mode 100644 index 0000000000000..7ef5e8318040a --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebasedwildcard.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [isTimeBasedWildcard](./kibana-plugin-plugins-data-server.indexpattern.istimebasedwildcard.md) + +## IndexPattern.isTimeBasedWildcard() method + +Signature: + +```typescript +isTimeBasedWildcard(): boolean; +``` +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md new file mode 100644 index 0000000000000..22fb60eba4f6e --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [isTimeNanosBased](./kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md) + +## IndexPattern.isTimeNanosBased() method + +Signature: + +```typescript +isTimeNanosBased(): boolean; +``` +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md new file mode 100644 index 0000000000000..d877854444a09 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md @@ -0,0 +1,60 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) + +## IndexPattern class + +Signature: + +```typescript +export declare class IndexPattern implements IIndexPattern +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, })](./kibana-plugin-plugins-data-server.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md) | | Record<string, any> | | +| [fields](./kibana-plugin-plugins-data-server.indexpattern.fields.md) | | IIndexPatternFieldList & {
toSpec: () => IndexPatternFieldMap;
} | | +| [flattenHit](./kibana-plugin-plugins-data-server.indexpattern.flattenhit.md) | | (hit: Record<string, any>, deep?: boolean) => Record<string, any> | | +| [formatField](./kibana-plugin-plugins-data-server.indexpattern.formatfield.md) | | FormatFieldFn | | +| [formatHit](./kibana-plugin-plugins-data-server.indexpattern.formathit.md) | | {
(hit: Record<string, any>, type?: string): any;
formatField: FormatFieldFn;
} | | +| [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md) | | () => {
title?: string | undefined;
timeFieldName?: string | undefined;
intervalName?: string | undefined;
fields?: string | undefined;
sourceFilters?: string | undefined;
fieldFormatMap?: string | undefined;
typeMeta?: string | undefined;
type?: string | undefined;
} | Get last saved saved object fields | +| [id](./kibana-plugin-plugins-data-server.indexpattern.id.md) | | string | | +| [intervalName](./kibana-plugin-plugins-data-server.indexpattern.intervalname.md) | | string | undefined | | +| [metaFields](./kibana-plugin-plugins-data-server.indexpattern.metafields.md) | | string[] | | +| [resetOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md) | | () => void | Reset last saved saved object fields. used after saving | +| [sourceFilters](./kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md) | | SourceFilter[] | | +| [timeFieldName](./kibana-plugin-plugins-data-server.indexpattern.timefieldname.md) | | string | undefined | | +| [title](./kibana-plugin-plugins-data-server.indexpattern.title.md) | | string | | +| [type](./kibana-plugin-plugins-data-server.indexpattern.type.md) | | string | undefined | | +| [typeMeta](./kibana-plugin-plugins-data-server.indexpattern.typemeta.md) | | TypeMeta | | +| [version](./kibana-plugin-plugins-data-server.indexpattern.version.md) | | string | undefined | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [addScriptedField(name, script, fieldType, lang)](./kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md) | | Add scripted field to field list | +| [getAggregationRestrictions()](./kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md) | | | +| [getAsSavedObjectBody()](./kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md) | | Returns index pattern as saved object body for saving | +| [getComputedFields()](./kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md) | | | +| [getFieldByName(name)](./kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md) | | | +| [getFormatterForField(field)](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md) | | Provide a field, get its formatter | +| [getNonScriptedFields()](./kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md) | | | +| [getScriptedFields()](./kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md) | | | +| [getSourceFiltering()](./kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md) | | Get the source filtering configuration for that index. | +| [getTimeField()](./kibana-plugin-plugins-data-server.indexpattern.gettimefield.md) | | | +| [isTimeBased()](./kibana-plugin-plugins-data-server.indexpattern.istimebased.md) | | | +| [isTimeBasedWildcard()](./kibana-plugin-plugins-data-server.indexpattern.istimebasedwildcard.md) | | | +| [isTimeNanosBased()](./kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md) | | | +| [popularizeField(fieldName, unit)](./kibana-plugin-plugins-data-server.indexpattern.popularizefield.md) | | | +| [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | +| [toSpec()](./kibana-plugin-plugins-data-server.indexpattern.tospec.md) | | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.metafields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.metafields.md new file mode 100644 index 0000000000000..a2c7c806d6057 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.metafields.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [metaFields](./kibana-plugin-plugins-data-server.indexpattern.metafields.md) + +## IndexPattern.metaFields property + +Signature: + +```typescript +metaFields: string[]; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.popularizefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.popularizefield.md new file mode 100644 index 0000000000000..8b2c9242a6256 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.popularizefield.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [popularizeField](./kibana-plugin-plugins-data-server.indexpattern.popularizefield.md) + +## IndexPattern.popularizeField() method + +Signature: + +```typescript +popularizeField(fieldName: string, unit?: number): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | | +| unit | number | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md new file mode 100644 index 0000000000000..3162a7f42dd12 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [removeScriptedField](./kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md) + +## IndexPattern.removeScriptedField() method + +Remove scripted field from field list + +Signature: + +```typescript +removeScriptedField(fieldName: string): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md new file mode 100644 index 0000000000000..18ec7070bd577 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [resetOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md) + +## IndexPattern.resetOriginalSavedObjectBody property + +Reset last saved saved object fields. used after saving + +Signature: + +```typescript +resetOriginalSavedObjectBody: () => void; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md new file mode 100644 index 0000000000000..d359bef2f30a9 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [sourceFilters](./kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md) + +## IndexPattern.sourceFilters property + +Signature: + +```typescript +sourceFilters?: SourceFilter[]; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.timefieldname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.timefieldname.md new file mode 100644 index 0000000000000..35740afa4e3dc --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.timefieldname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [timeFieldName](./kibana-plugin-plugins-data-server.indexpattern.timefieldname.md) + +## IndexPattern.timeFieldName property + +Signature: + +```typescript +timeFieldName: string | undefined; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.title.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.title.md new file mode 100644 index 0000000000000..4cebde989aebd --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.title.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [title](./kibana-plugin-plugins-data-server.indexpattern.title.md) + +## IndexPattern.title property + +Signature: + +```typescript +title: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.tospec.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.tospec.md new file mode 100644 index 0000000000000..5d76b8f00853b --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.tospec.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [toSpec](./kibana-plugin-plugins-data-server.indexpattern.tospec.md) + +## IndexPattern.toSpec() method + +Signature: + +```typescript +toSpec(): IndexPatternSpec; +``` +Returns: + +`IndexPatternSpec` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.type.md new file mode 100644 index 0000000000000..01154ab5444d1 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [type](./kibana-plugin-plugins-data-server.indexpattern.type.md) + +## IndexPattern.type property + +Signature: + +```typescript +type: string | undefined; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.typemeta.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.typemeta.md new file mode 100644 index 0000000000000..b16bcec404d97 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.typemeta.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [typeMeta](./kibana-plugin-plugins-data-server.indexpattern.typemeta.md) + +## IndexPattern.typeMeta property + +Signature: + +```typescript +typeMeta?: TypeMeta; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.version.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.version.md new file mode 100644 index 0000000000000..e4297d8389111 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.version.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [version](./kibana-plugin-plugins-data-server.indexpattern.version.md) + +## IndexPattern.version property + +Signature: + +```typescript +version: string | undefined; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md index 4a5b61f5c179b..40b029da00469 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md @@ -4,12 +4,6 @@ ## IndexPatternAttributes interface -> Warning: This API is now obsolete. -> -> - -Use data plugin interface instead - Signature: ```typescript diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.aggregatable.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.aggregatable.md deleted file mode 100644 index 92994b851ec85..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.aggregatable.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [aggregatable](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.aggregatable.md) - -## IndexPatternFieldDescriptor.aggregatable property - -Signature: - -```typescript -aggregatable: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.estypes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.estypes.md deleted file mode 100644 index f24ba9a48d85e..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.estypes.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [esTypes](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.estypes.md) - -## IndexPatternFieldDescriptor.esTypes property - -Signature: - -```typescript -esTypes: string[]; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md deleted file mode 100644 index d84d0cba06ac6..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) - -## IndexPatternFieldDescriptor interface - -Signature: - -```typescript -export interface FieldDescriptor -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [aggregatable](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.aggregatable.md) | boolean | | -| [esTypes](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.estypes.md) | string[] | | -| [name](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.name.md) | string | | -| [readFromDocValues](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.readfromdocvalues.md) | boolean | | -| [searchable](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.searchable.md) | boolean | | -| [subType](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.subtype.md) | FieldSubType | | -| [type](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.type.md) | string | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.name.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.name.md deleted file mode 100644 index 16ea60c5b8ae2..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [name](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.name.md) - -## IndexPatternFieldDescriptor.name property - -Signature: - -```typescript -name: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.readfromdocvalues.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.readfromdocvalues.md deleted file mode 100644 index fc8667196c879..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.readfromdocvalues.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [readFromDocValues](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.readfromdocvalues.md) - -## IndexPatternFieldDescriptor.readFromDocValues property - -Signature: - -```typescript -readFromDocValues: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.searchable.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.searchable.md deleted file mode 100644 index 7d159c65b40bd..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.searchable.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [searchable](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.searchable.md) - -## IndexPatternFieldDescriptor.searchable property - -Signature: - -```typescript -searchable: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.subtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.subtype.md deleted file mode 100644 index 7053eaf08138c..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.subtype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [subType](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.subtype.md) - -## IndexPatternFieldDescriptor.subType property - -Signature: - -```typescript -subType?: FieldSubType; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.type.md deleted file mode 100644 index bb571d1bee14a..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [type](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.type.md) - -## IndexPatternFieldDescriptor.type property - -Signature: - -```typescript -type: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.md new file mode 100644 index 0000000000000..aa78c055f4f5c --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) + +## IndexPatternsService class + +Signature: + +```typescript +export declare class IndexPatternsService implements Plugin +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [setup(core)](./kibana-plugin-plugins-data-server.indexpatternsservice.setup.md) | | | +| [start(core, { fieldFormats, logger })](./kibana-plugin-plugins-data-server.indexpatternsservice.start.md) | | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setup.md new file mode 100644 index 0000000000000..a354fbc2a477b --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setup.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [setup](./kibana-plugin-plugins-data-server.indexpatternsservice.setup.md) + +## IndexPatternsService.setup() method + +Signature: + +```typescript +setup(core: CoreSetup): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| core | CoreSetup | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md new file mode 100644 index 0000000000000..d35dc3aa11000 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [start](./kibana-plugin-plugins-data-server.indexpatternsservice.start.md) + +## IndexPatternsService.start() method + +Signature: + +```typescript +start(core: CoreStart, { fieldFormats, logger }: IndexPatternsServiceStartDeps): { + indexPatternsServiceFactory: (kibanaRequest: KibanaRequest) => Promise; + }; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| core | CoreStart | | +| { fieldFormats, logger } | IndexPatternsServiceStartDeps | | + +Returns: + +`{ + indexPatternsServiceFactory: (kibanaRequest: KibanaRequest) => Promise; + }` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md index ac2ae13372f7a..3e27140e8bc08 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md @@ -15,6 +15,6 @@ export interface ISearchSetup | Property | Type | Description | | --- | --- | --- | | [aggs](./kibana-plugin-plugins-data-server.isearchsetup.aggs.md) | AggsSetup | | -| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | <SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse>(name: string, strategy: ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse>) => void | Extension point exposed for other plugins to register their own search strategies. | +| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | <SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse>(name: string, strategy: ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse>) => void | Extension point exposed for other plugins to register their own search strategies. | | [usage](./kibana-plugin-plugins-data-server.isearchsetup.usage.md) | SearchUsage | Used internally for telemetry | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md index f20c6f4911062..81571d343495c 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md @@ -9,5 +9,5 @@ Extension point exposed for other plugins to register their own search strategie Signature: ```typescript -registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void; +registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md index 577532d22b3d3..b8b6ee1f0b28c 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md @@ -7,7 +7,7 @@ Signature: ```typescript -export interface ISearchStart +export interface ISearchStart ``` ## Properties @@ -16,5 +16,5 @@ export interface ISearchStartAggsStart | | | [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | (name: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse> | Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. | -| [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) | (context: RequestHandlerContext, request: IEsSearchRequest, options: ISearchOptions) => Promise<IEsSearchResponse> | | +| [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) | (context: RequestHandlerContext, request: SearchStrategyRequest, options: ISearchOptions) => Promise<SearchStrategyResponse> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md index 33ca818afc769..fdcd4d6768db5 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md @@ -7,5 +7,5 @@ Signature: ```typescript -search: (context: RequestHandlerContext, request: IEsSearchRequest, options: ISearchOptions) => Promise; +search: (context: RequestHandlerContext, request: SearchStrategyRequest, options: ISearchOptions) => Promise; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md index dc076455ab272..3d2caf417f3cb 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md @@ -9,7 +9,7 @@ Search strategy interface contains a search method that takes in a request and r Signature: ```typescript -export interface ISearchStrategy +export interface ISearchStrategy ``` ## Properties diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 3c477e17503f4..f1eecd6e49b02 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -9,7 +9,9 @@ | Class | Description | | --- | --- | | [AggParamType](./kibana-plugin-plugins-data-server.aggparamtype.md) | | +| [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) | | | [IndexPatternsFetcher](./kibana-plugin-plugins-data-server.indexpatternsfetcher.md) | | +| [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) | | | [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md) | | | [Plugin](./kibana-plugin-plugins-data-server.plugin.md) | | @@ -41,14 +43,12 @@ | --- | --- | | [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) | | | [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | | +| [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-server.fieldformatconfig.md) | | | [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) | | -| [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) | | | [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) | | -| [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) | | -| [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | Use data plugin interface instead | -| [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) | | +| [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | | | [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) | | | [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) | | | [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | | @@ -91,6 +91,7 @@ | [Filter](./kibana-plugin-plugins-data-server.filter.md) | | | [IAggConfig](./kibana-plugin-plugins-data-server.iaggconfig.md) | AggConfig This class represents an aggregation, which is displayed in the left-hand nav of the Visualize app. | | [IAggType](./kibana-plugin-plugins-data-server.iaggtype.md) | | +| [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) | | | [IFieldFormatsRegistry](./kibana-plugin-plugins-data-server.ifieldformatsregistry.md) | | | [IFieldParamType](./kibana-plugin-plugins-data-server.ifieldparamtype.md) | | | [IMetricAggType](./kibana-plugin-plugins-data-server.imetricaggtype.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 455c5ecdd8195..84aeb4cf80cce 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -13,7 +13,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; + indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; }; }; ``` @@ -32,7 +32,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; + indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; }; }` diff --git a/docs/getting-started/tutorial-define-index.asciidoc b/docs/getting-started/tutorial-define-index.asciidoc index fbe7450683dbc..cb3f6c9ff0c9b 100644 --- a/docs/getting-started/tutorial-define-index.asciidoc +++ b/docs/getting-started/tutorial-define-index.asciidoc @@ -47,5 +47,11 @@ contains the time series data. [role="screenshot"] image::images/tutorial_index_patterns.png[All tutorial index patterns] +NOTE: When you define an index pattern, the indices that match that pattern must +exist in Elasticsearch and they must contain data. To check if the indices are +available, open the menu, go to *Dev Tools > Console*, then enter `GET _cat/indices`. Alternately, use +`curl -XGET "http://localhost:9200/_cat/indices"`. +For Windows, run `Invoke-RestMethod -Uri "http://localhost:9200/_cat/indices"` in Powershell. + diff --git a/docs/getting-started/tutorial-full-experience.asciidoc b/docs/getting-started/tutorial-full-experience.asciidoc index 1e6fe39dbd013..a7d5412ae0632 100644 --- a/docs/getting-started/tutorial-full-experience.asciidoc +++ b/docs/getting-started/tutorial-full-experience.asciidoc @@ -25,7 +25,14 @@ curl -O https://download.elastic.co/demos/kibana/gettingstarted/8.x/shakespeare. curl -O https://download.elastic.co/demos/kibana/gettingstarted/8.x/accounts.zip curl -O https://download.elastic.co/demos/kibana/gettingstarted/8.x/logs.jsonl.gz -Two of the data sets are compressed. To extract the files, use the following commands: +Alternatively, for Windows users, run the following commands in Powershell: + +[source,shell] +Invoke-RestMethod https://download.elastic.co/demos/kibana/gettingstarted/8.x/shakespeare.json -OutFile shakespeare.json +Invoke-RestMethod https://download.elastic.co/demos/kibana/gettingstarted/8.x/accounts.zip -OutFile accounts.zip +Invoke-RestMethod https://download.elastic.co/demos/kibana/gettingstarted/8.x/logs.jsonl.gz -OutFile logs.jsonl.gz + +Two of the data sets are compressed. To extract the files, use these commands: [source,shell] unzip accounts.zip diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index ed20166c87f29..1bae04cc2e58b 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -1,9 +1,10 @@ [[advanced-options]] == Advanced Settings -The *Advanced Settings* UI enables you to edit settings that control the behavior of Kibana. -For example, you can change the format used to display dates, specify the default index pattern, and set the precision -for displayed decimal values. +The *Advanced Settings* UI enables you to edit settings that control the +behavior of Kibana. For example, you can change the format used to display dates, +specify the default index pattern, and set the precision for displayed decimal +values. . Open the menu, then go to *Stack Management > {kib} > Advanced Settings*. . Scroll or search for the setting you want to modify. @@ -15,8 +16,9 @@ for displayed decimal values. [[settings-read-only-access]] === [xpack]#Read only access# When you have insufficient privileges to edit advanced settings, the following -indicator in Kibana will be displayed. The buttons to edit settings won't be visible. -For more information on granting access to Kibana see <>. +indicator in Kibana will be displayed. The buttons to edit settings won't be +visible. For more information on granting access to Kibana, see +<>. [role="screenshot"] image::images/settings-read-only-badge.png[Example of Advanced Settings Management's read only access indicator in Kibana's header] @@ -25,12 +27,11 @@ image::images/settings-read-only-badge.png[Example of Advanced Settings Manageme [[kibana-settings-reference]] === Kibana settings reference -WARNING: Modifying a setting can affect {kib} -performance and cause problems that are -difficult to diagnose. Setting a property value to a blank field reverts -to the default behavior, which might not be -compatible with other configuration settings. Deleting a custom setting -removes it from {kib} permanently. +WARNING: Modifying a setting can affect {kib} performance and cause problems +that are difficult to diagnose. Setting a property value to a blank field +reverts to the default behavior, which might not be compatible with other +configuration settings. Deleting a custom setting removes it from {kib} +permanently. [float] @@ -38,72 +39,159 @@ removes it from {kib} permanently. ==== General [horizontal] -`csv:quoteValues`:: Set this property to `true` to quote exported values. -`csv:separator`:: A string that serves as the separator for exported values. -`dateFormat`:: The format to use for displaying https://momentjs.com/docs/#/displaying/format/[pretty formatted dates]. -`dateFormat:dow`:: The day that a week should start on. -`dateFormat:scaled`:: The values that define the format to use to render ordered time-based data. Formatted timestamps must -adapt to the interval between measurements. Keys are http://en.wikipedia.org/wiki/ISO_8601#Time_intervals[ISO8601 intervals]. -`dateFormat:tz`:: The timezone that Kibana uses. The default value of `Browser` uses the timezone detected by the browser. -`dateNanosFormat`:: The format to use for displaying https://momentjs.com/docs/#/displaying/format/[pretty formatted dates] of {ref}/date_nanos.html[Elasticsearch date_nanos type]. -`defaultIndex`:: The index to access if no index is set. The default is `null`. -`defaultRoute`:: The default route when opening Kibana. Use this setting to route users to a specific dashboard, application, or saved object as they enter each space. -`fields:popularLimit`:: The top N most popular fields to show. -`filterEditor:suggestValues`:: Set this property to `false` to prevent the filter editor from suggesting values for fields. -`filters:pinnedByDefault`:: Set this property to `true` to make filters have a global state (be pinned) by default. -`format:bytes:defaultPattern`:: The default <> format for the "bytes" format. -`format:currency:defaultPattern`:: The default <> format for the "currency" format. -`format:defaultTypeMap`:: A map of the default format name for each field type. Field types that are not explicitly -mentioned use "\_default_". -`format:number:defaultLocale`:: The <> locale. -`format:number:defaultPattern`:: The <> for the "number" format. -`format:percent:defaultPattern`:: The <> for the "percent" format. -`histogram:barTarget`:: When date histograms use the `auto` interval, Kibana attempts to generate this number of bars. -`histogram:maxBars`:: Date histograms are not generated with more bars than the value of this property, scaling values -when necessary. -`history:limit`:: In fields that have history, such as query inputs, show this many recent values. -`indexPattern:placeholder`:: The default placeholder value to use in Management > Index Patterns > Create Index Pattern. -`metaFields`:: Fields that exist outside of `_source`. Kibana merges these fields -into the document when displaying it. -`metrics:max_buckets`:: The maximum numbers of buckets that a single -data source can return. This might arise when the user selects a -short interval (for example, 1s) for a long time period (1 year). -`pageNavigation`:: The style of navigation menu for Kibana. -Choices are Legacy, the legacy style where every plugin is represented in the nav, -and Modern, a new format that bundles related plugins together in flyaway nested navigation. -`query:allowLeadingWildcards`:: Allows a wildcard (*) as the first character -in a query clause. Only applies when experimental query features are -enabled in the query bar. To disallow leading wildcards in Lucene queries, -use `query:queryString:options`. -`query:queryString:options`:: Options for the Lucene query string parser. Only -used when "Query language" is set to Lucene. -`savedObjects:listingLimit`:: The number of objects to fetch for lists of saved objects. -The default value is 1000. Do not set above 10000. -`savedObjects:perPage`:: The number of objects to show on each page of the -list of saved objects. The default is 5. -`search:queryLanguage`:: The query language to use in the query bar. -Choices are <>, a language built specifically for {kib}, and the <>. -`shortDots:enable`:: Set this property to `true` to shorten long -field names in visualizations. For example, show `f.b.baz` instead of `foo.bar.baz`. -`sort:options`:: Options for the Elasticsearch {ref}/search-request-body.html#request-body-search-sort[sort] parameter. -`state:storeInSessionStorage`:: [experimental] Kibana tracks UI state in the -URL, which can lead to problems when there is a lot of state information, -and the URL gets very long. -Enabling this setting stores part of the URL in your browser session to keep the -URL short. -`theme:darkMode`:: Set to `true` to enable a dark mode for the {kib} UI. You must -refresh the page to apply the setting. -`timepicker:quickRanges`:: The list of ranges to show in the Quick section of -the time filter. This should be an array of objects, with each object containing -`from`, `to` (see {ref}/common-options.html#date-math[accepted formats]), -and `display` (the title to be displayed). -`timepicker:refreshIntervalDefaults`:: The default refresh interval for the time filter. Example: `{ "display": "15 seconds", "pause": true, "value": 15000 }`. -`timepicker:timeDefaults`:: The default selection in the time filter. -`truncate:maxHeight`:: The maximum height that a cell occupies in a table. Set to 0 to disable +[[csv-quotevalues]]`csv:quoteValues`:: +Set this property to `true` to quote exported values. + +[[csv-separator]]`csv:separator`:: +A string that serves as the separator for exported values. + +[[dateformat]]`dateFormat`:: +The format to use for displaying +https://momentjs.com/docs/#/displaying/format/[pretty formatted dates]. + +[[dateformat-dow]]`dateFormat:dow`:: +The day that a week should start on. + +[[dateformat-scaled]]`dateFormat:scaled`:: +The values that define the format to use to render ordered time-based data. +Formatted timestamps must adapt to the interval between measurements. Keys are +http://en.wikipedia.org/wiki/ISO_8601#Time_intervals[ISO8601 intervals]. + +[[dateformat-tz]]`dateFormat:tz`:: +The timezone that Kibana uses. The default value of `Browser` uses the timezone +detected by the browser. + +[[datenanosformat]]`dateNanosFormat`:: +The format to use for displaying +https://momentjs.com/docs/#/displaying/format/[pretty formatted dates] of +{ref}/date_nanos.html[Elasticsearch date_nanos type]. + +[[defaultindex]]`defaultIndex`:: +The index to access if no index is set. The default is `null`. + +[[defaultroute]]`defaultRoute`:: +The default route when opening Kibana. Use this setting to route users to a +specific dashboard, application, or saved object as they enter each space. + +[[fields-popularlimit]]`fields:popularLimit`:: +The top N most popular fields to show. + +[[filtereditor-suggestvalues]]`filterEditor:suggestValues`:: +Set this property to `false` to prevent the filter editor from suggesting values +for fields. + +[[filters-pinnedbydefault]]`filters:pinnedByDefault`:: +Set this property to `true` to make filters have a global state (be pinned) by +default. + +[[format-bytes-defaultpattern]]`format:bytes:defaultPattern`:: +The default <> format for the "bytes" format. + +[[format-currency-defaultpattern]]`format:currency:defaultPattern`:: +The default <> format for the "currency" format. + +[[format-defaulttypemap]]`format:defaultTypeMap`:: +A map of the default format name for each field type. Field types that are not +explicitly mentioned use "\_default_". + +[[format-number-defaultlocale]]`format:number:defaultLocale`:: +The <> locale. + +[[format-number-defaultpattern]]`format:number:defaultPattern`:: +The <> for the "number" format. + +[[format-percent-defaultpattern]]`format:percent:defaultPattern`:: +The <> for the "percent" format. + +[[histogram-bartarget]]`histogram:barTarget`:: +When date histograms use the `auto` interval, Kibana attempts to generate this +number of bars. + +[[histogram-maxbars]]`histogram:maxBars`:: +Date histograms are not generated with more bars than the value of this property, +scaling values when necessary. + +[[history-limit]]`history:limit`:: +In fields that have history, such as query inputs, show this many recent values. + +[[indexpattern-placeholder]]`indexPattern:placeholder`:: +The default placeholder value to use in +*Management > Index Patterns > Create Index Pattern*. + +[[metafields]]`metaFields`:: +Fields that exist outside of `_source`. Kibana merges these fields into the +document when displaying it. + +[[metrics-maxbuckets]]`metrics:max_buckets`:: +The maximum numbers of buckets that a single data source can return. This might +arise when the user selects a short interval (for example, 1s) for a long time +period (1 year). + +[[pagenavigation]]`pageNavigation`:: +The style of navigation menu for Kibana. Choices are Legacy, the legacy style +where every plugin is represented in the nav, and Modern, a new format that +bundles related plugins together in flyaway nested navigation. + +[[query-allowleadingwildcards]]`query:allowLeadingWildcards`:: +Allows a wildcard (*) as the first character in a query clause. Only applies +when experimental query features are enabled in the query bar. To disallow +leading wildcards in Lucene queries, use `query:queryString:options`. + +[[query-querystring-options]]`query:queryString:options`:: +Options for the Lucene query string parser. Only used when "Query language" is +set to Lucene. + +[[savedobjects-listinglimit]]`savedObjects:listingLimit`:: +The number of objects to fetch for lists of saved objects. The default value +is 1000. Do not set above 10000. + +[[savedobjects-perpage]]`savedObjects:perPage`:: +The number of objects to show on each page of the list of saved objects. The +default is 5. + +[[search-querylanguage]]`search:queryLanguage`:: +The query language to use in the query bar. Choices are <>, a +language built specifically for {kib}, and the +<>. + +[[shortdots-enable]]`shortDots:enable`:: +Set this property to `true` to shorten long field names in visualizations. For +example, show `f.b.baz` instead of `foo.bar.baz`. + +[[sort-options]]`sort:options`:: Options for the Elasticsearch +{ref}/search-request-body.html#request-body-search-sort[sort] parameter. + +[[state-storeinsessionstorage]]`state:storeInSessionStorage`:: +experimental:[] +Kibana tracks UI state in the URL, which can lead to problems +when there is a lot of state information, and the URL gets very long. Enabling +this setting stores part of the URL in your browser session to keep the URL +short. + +[[theme-darkmode]]`theme:darkMode`:: +Set to `true` to enable a dark mode for the {kib} UI. You must refresh the page +to apply the setting. + +[[timepicker-quickranges]]`timepicker:quickRanges`:: +The list of ranges to show in the Quick section of the time filter. This should +be an array of objects, with each object containing `from`, `to` (see +{ref}/common-options.html#date-math[accepted formats]), and `display` (the title +to be displayed). + +[[timepicker-refreshintervaldefaults]]`timepicker:refreshIntervalDefaults`:: +The default refresh interval for the time filter. Example: +`{ "display": "15 seconds", "pause": true, "value": 15000 }`. + +[[timepicker-timedefaults]]`timepicker:timeDefaults`:: +The default selection in the time filter. + +[[truncate-maxheight]]`truncate:maxHeight`:: +The maximum height that a cell occupies in a table. Set to 0 to disable truncation. -`xPack:defaultAdminEmail`:: Email address for X-Pack admin operations, such as -cluster alert notifications from Monitoring. + +[[xpack-defaultadminemail]]`xPack:defaultAdminEmail`:: +Email address for {xpack} admin operations, such as cluster alert notifications +from *{stack-monitor-app}*. [float] @@ -111,15 +199,17 @@ cluster alert notifications from Monitoring. ==== Accessibility [horizontal] -`accessibility:disableAnimations`:: Turns off all unnecessary animations in the -{kib} UI. Refresh the page to apply the changes. +[[accessibility-disableanimations]]`accessibility:disableAnimations`:: +Turns off all unnecessary animations in the {kib} UI. Refresh the page to apply +the changes. [float] [[kibana-dashboard-settings]] ==== Dashboard [horizontal] -`xpackDashboardMode:roles`:: **Deprecated. Use <> instead.** +[[xpackdashboardmode-roles]]`xpackDashboardMode:roles`:: +**Deprecated. Use <> instead.** The roles that belong to <>. [float] @@ -127,38 +217,63 @@ The roles that belong to <>. ==== Discover [horizontal] -`context:defaultSize`:: The number of surrounding entries to display in the context view. The default value is 5. -`context:step`:: The number by which to increment or decrement the context size. The default value is 5. -`context:tieBreakerFields`:: A comma-separated list of fields to use -for breaking a tie between documents that have the same timestamp value. The first -field that is present and sortable in the current index pattern is used. -`defaultColumns`:: The columns that appear by default on the Discover page. -The default is `_source`. -`discover:aggs:terms:size`:: The number terms that are visualized when clicking -the Visualize button in the field drop down. The default is `20`. -`discover:sampleSize`:: The number of rows to show in the Discover table. -`discover:sort:defaultOrder`:: The default sort direction for time-based index patterns. -`discover:searchOnPageLoad`:: Controls whether a search is executed when Discover first loads. -This setting does not have an effect when loading a saved search. -`doc_table:hideTimeColumn`:: Hides the "Time" column in Discover and in all saved searches on dashboards. -`doc_table:highlight`:: Highlights results in Discover and saved searches on dashboards. -Highlighting slows requests when -working on big documents. +[[context-defaultsize]]`context:defaultSize`:: +The number of surrounding entries to display in the context view. The default +value is 5. + +[[context-step]]`context:step`:: +The number by which to increment or decrement the context size. The default +value is 5. + +[[context-tiebreakerfields]]`context:tieBreakerFields`:: +A comma-separated list of fields to use for breaking a tie between documents +that have the same timestamp value. The first field that is present and sortable +in the current index pattern is used. + +[[defaultcolumns]]`defaultColumns`:: +The columns that appear by default on the *Discover* page. The default is +`_source`. + +[[discover-aggs-terms-size]]`discover:aggs:terms:size`:: +The number terms that are visualized when clicking the *Visualize* button in the +field drop down. The default is `20`. + +[[discover-samplesize]]`discover:sampleSize`:: +The number of rows to show in the *Discover* table. + +[[discover-sort-defaultorder]]`discover:sort:defaultOrder`:: +The default sort direction for time-based index patterns. + +[[discover-searchonpageload]]`discover:searchOnPageLoad`:: +Controls whether a search is executed when *Discover* first loads. This setting +does not have an effect when loading a saved search. + +[[doctable-hidetimecolumn]]`doc_table:hideTimeColumn`:: +Hides the "Time" column in *Discover* and in all saved searches on dashboards. + +[[doctable-highlight]]`doc_table:highlight`:: +Highlights results in *Discover* and saved searches on dashboards. Highlighting +slows requests when working on big documents. [float] [[kibana-ml-settings]] ==== Machine learning [horizontal] -`ml:anomalyDetection:results:enableTimeDefaults`:: Use the default time filter -in the *Single Metric Viewer* and *Anomaly Explorer*. If this setting is -disabled, the results for the full time range are shown. -`ml:anomalyDetection:results:timeDefaults`:: Sets the default time filter for -viewing {anomaly-job} results. This setting must contain `from` and `to` values (see {ref}/common-options.html#date-math[accepted formats]). It is ignored -unless `ml:anomalyDetection:results:enableTimeDefaults` is enabled. -`ml:fileDataVisualizerMaxFileSize`:: Sets the file size limit when importing -data in the {data-viz}. The default value is `100MB`. The highest supported -value for this setting is `1GB`. +[[ml-anomalydetection-results-enabletimedefaults]]`ml:anomalyDetection:results:enableTimeDefaults`:: +Use the default time filter in the *Single Metric Viewer* and +*Anomaly Explorer*. If this setting is disabled, the results for the full time +range are shown. + +[[ml-anomalydetection-results-timedefaults]]`ml:anomalyDetection:results:timeDefaults`:: +Sets the default time filter for viewing {anomaly-job} results. This setting +must contain `from` and `to` values (see +{ref}/common-options.html#date-math[accepted formats]). It is ignored unless +`ml:anomalyDetection:results:enableTimeDefaults` is enabled. + +[[ml-filedatavisualizermaxfilesize]]`ml:fileDataVisualizerMaxFileSize`:: +Sets the file size limit when importing data in the {data-viz}. The default +value is `100MB`. The highest supported value for this setting is `1GB`. [float] @@ -166,18 +281,26 @@ value for this setting is `1GB`. ==== Notifications [horizontal] -`notifications:banner`:: A custom banner intended for temporary notices to all users. -Supports https://help.github.com/en/articles/basic-writing-and-formatting-syntax[Markdown]. -`notifications:lifetime:banner`:: The duration, in milliseconds, for banner -notification displays. The default value is 3000000. Set this field to `Infinity` -to disable banner notifications. -`notifications:lifetime:error`:: The duration, in milliseconds, for error -notification displays. The default value is 300000. Set this field to `Infinity` to disable error notifications. -`notifications:lifetime:info`:: The duration, in milliseconds, for information notification displays. -The default value is 5000. Set this field to `Infinity` to disable information notifications. -`notifications:lifetime:warning`:: The duration, in milliseconds, for warning notification -displays. The default value is 10000. Set this field to `Infinity` to disable warning notifications. +[[notifications-banner]]`notifications:banner`:: +A custom banner intended for temporary notices to all users. Supports +https://help.github.com/en/articles/basic-writing-and-formatting-syntax[Markdown]. +[[notifications-lifetime-banner]]`notifications:lifetime:banner`:: +The duration, in milliseconds, for banner notification displays. The default +value is 3000000. Set this field to `Infinity` to disable banner notifications. + +[[notificatios-lifetime-error]]`notifications:lifetime:error`:: +The duration, in milliseconds, for error notification displays. The default +value is 300000. Set this field to `Infinity` to disable error notifications. + +[[notifications-lifetime-info]]`notifications:lifetime:info`:: +The duration, in milliseconds, for information notification displays. The +default value is 5000. Set this field to `Infinity` to disable information +notifications. + +[[notifications-lifetime-warning]]`notifications:lifetime:warning`:: +The duration, in milliseconds, for warning notification displays. The default +value is 10000. Set this field to `Infinity` to disable warning notifications. [float] @@ -185,7 +308,8 @@ displays. The default value is 10000. Set this field to `Infinity` to disable wa ==== Reporting [horizontal] -`xpackReporting:customPdfLogo`:: A custom image to use in the footer of the PDF. +[[xpackreporting-custompdflogo]]`xpackReporting:customPdfLogo`:: +A custom image to use in the footer of the PDF. [float] @@ -193,9 +317,10 @@ displays. The default value is 10000. Set this field to `Infinity` to disable wa ==== Rollup [horizontal] -`rollups:enableIndexPatterns`:: Enables the creation of index patterns that -capture rollup indices, which in turn enables visualizations based on rollup data. -Refresh the page to apply the changes. +[[rollups-enableindexpatterns]]`rollups:enableIndexPatterns`:: +Enables the creation of index patterns that capture rollup indices, which in +turn enables visualizations based on rollup data. Refresh the page to apply the +changes. [float] @@ -203,67 +328,117 @@ Refresh the page to apply the changes. ==== Search [horizontal] -`courier:batchSearches`:: **Deprecated in 7.6. Starting in 8.0, this setting will be optimized internally.** -When disabled, dashboard panels will load individually, and search requests will terminate when -users navigate away or update the query. When enabled, dashboard panels will load together when all of the data is loaded, -and searches will not terminate. -`courier:customRequestPreference`:: {ref}/search-request-body.html#request-body-search-preference[Request preference] +[[courier-batchsearches]]`courier:batchSearches`:: +**Deprecated in 7.6. Starting in 8.0, this setting will be optimized internally.** +When disabled, dashboard panels will load individually, and search requests will +terminate when users navigate away or update the query. When enabled, dashboard +panels will load together when all of the data is loaded, and searches will not +terminate. + +[[courier-customrequestpreference]]`courier:customRequestPreference`:: +{ref}/search-request-body.html#request-body-search-preference[Request preference] to use when `courier:setRequestPreference` is set to "custom". -`courier:ignoreFilterIfFieldNotInIndex`:: Skips filters that apply to fields that don't exist in the index for a visualization. -Useful when dashboards consist of visualizations from multiple index patterns. -`courier:maxConcurrentShardRequests`:: Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests] + +[[courier-ignorefilteriffieldnotinindex]]`courier:ignoreFilterIfFieldNotInIndex`:: +Skips filters that apply to fields that don't exist in the index for a +visualization. Useful when dashboards consist of visualizations from multiple +index patterns. + +[[courier-maxconcurrentshardrequests]]`courier:maxConcurrentShardRequests`:: +Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests] setting used for `_msearch` requests sent by {kib}. Set to 0 to disable this config and use the {es} default. -`courier:setRequestPreference`:: Enables you to set which shards handle your search requests. -* *Session ID:* Restricts operations to execute all search requests on the same shards. -This has the benefit of reusing shard caches across requests. -* *Custom:* Allows you to define your own preference. Use `courier:customRequestPreference` -to customize your preference value. + +[[courier-setrequestpreference]]`courier:setRequestPreference`:: +Enables you to set which shards handle your search requests. +* *Session ID:* Restricts operations to execute all search requests on the same +shards. This has the benefit of reusing shard caches across requests. +* *Custom:* Allows you to define your own preference. Use +`courier:customRequestPreference` to customize your preference value. * *None:* Do not set a preference. This might provide better performance because requests can be spread across all shard copies. However, results might be inconsistent because different shards might be in different refresh states. -`search:includeFrozen`:: Includes {ref}/frozen-indices.html[frozen indices] in results. -Searching through frozen indices -might increase the search time. This setting is off by default. Users must opt-in to include frozen indices. -`search:timeout`:: Change the maximum timeout for a search session or set to 0 to disable the timeout and allow queries to run to completion. + +[[search-includefrozen]]`search:includeFrozen`:: +Includes {ref}/frozen-indices.html[frozen indices] in results. Searching through +frozen indices might increase the search time. This setting is off by default. +Users must opt-in to include frozen indices. + +[[search-timeout]]`search:timeout`:: Change the maximum timeout for a search +session or set to 0 to disable the timeout and allow queries to run to +completion. [float] [[kibana-siem-settings]] -==== Security Solution +==== Security solution [horizontal] -`securitySolution:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the Security app. -`securitySolution:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the Security app collects events. -`securitySolution:ipReputationLinks`:: A JSON array containing links for verifying the reputation of an IP address. The links are displayed on -{security-guide}/network-page-overview.html[IP detail] pages. -`securitySolution:enableNewsFeed`:: Enables the security news feed on the Security *Overview* -page. -`securitySolution:newsFeedUrl`:: The URL from which the security news feed content is -retrieved. -`securitySolution:refreshIntervalDefaults`:: The default refresh interval for the Security time filter, in milliseconds. -`securitySolution:timeDefaults`:: The default period of time in the Security time filter. +[[securitysolution-defaultanomalyscore]]`securitySolution:defaultAnomalyScore`:: +The threshold above which {ml} job anomalies are displayed in the {security-app}. + +[[securitysolution-defaultindex]]`securitySolution:defaultIndex`:: +A comma-delimited list of {es} indices from which the {security-app} collects +events. + +[[securitysolution-ipreputationlinks]]`securitySolution:ipReputationLinks`:: +A JSON array containing links for verifying the reputation of an IP address. The +links are displayed on {security-guide}/network-page-overview.html[IP detail] +pages. + +[[securitysolution-enablenewsfeed]]`securitySolution:enableNewsFeed`:: Enables +the security news feed on the Security *Overview* page. + +[[securitysolution-newsfeedurl]]`securitySolution:newsFeedUrl`:: +The URL from which the security news feed content is retrieved. + +[[securitysolution-refreshintervaldefaults]]`securitySolution:refreshIntervalDefaults`:: +The default refresh interval for the Security time filter, in milliseconds. + +[[securitysolution-timedefaults]]`securitySolution:timeDefaults`:: +The default period of time in the Security time filter. [float] [[kibana-timelion-settings]] ==== Timelion [horizontal] -`timelion:default_columns`:: The default number of columns to use on a Timelion sheet. -`timelion:default_rows`:: The default number of rows to use on a Timelion sheet. -`timelion:es.default_index`:: The default index when using the `.es()` query. -`timelion:es.timefield`:: The default field containing a timestamp when using the `.es()` query. -`timelion:graphite.url`:: [experimental] Used with graphite queries, this is the URL of your graphite host -in the form https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite. This URL can be -selected from an allow-list configured in the `kibana.yml` under `timelion.graphiteUrls`. -`timelion:max_buckets`:: The maximum number of buckets a single data source can return. -This value is used for calculating automatic intervals in visualizations. -`timelion:min_interval`:: The smallest interval to calculate when using "auto". -`timelion:quandl.key`:: [experimental] Used with quandl queries, this is your API key from https://www.quandl.com/[www.quandl.com]. -`timelion:showTutorial`:: Shows the Timelion tutorial -to users when they first open the Timelion app. -`timelion:target_buckets`:: Used for calculating automatic intervals in visualizations, -this is the number of buckets to try to represent. +[[timelion-defaultcolumns]]`timelion:default_columns`:: +The default number of columns to use on a Timelion sheet. + +[[timelion-defaultrows]]`timelion:default_rows`:: +The default number of rows to use on a Timelion sheet. + +[[timelion-esdefaultindex]]`timelion:es.default_index`:: +The default index when using the `.es()` query. + +[[timelion-estimefield]]`timelion:es.timefield`:: +The default field containing a timestamp when using the `.es()` query. + +[[timelion-graphite-url]]`timelion:graphite.url`:: +experimental:[] +Used with graphite queries, this is the URL of your graphite host +in the form https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite. This URL can +be selected from an allow-list configured in the `kibana.yml` under +`timelion.graphiteUrls`. + +[[timelion-maxbuckets]]`timelion:max_buckets`:: +The maximum number of buckets a single data source can return. This value is +used for calculating automatic intervals in visualizations. +[[timelion-mininterval]]`timelion:min_interval`:: +The smallest interval to calculate when using "auto". + +[[timelion-quandlkey]]`timelion:quandl.key`:: +experimental:[] +Used with quandl queries, this is your API key from +https://www.quandl.com/[www.quandl.com]. + +[[timelion-showtutorial]]`timelion:showTutorial`:: +Shows the Timelion tutorial to users when they first open the Timelion app. + +[[timelion-targetbuckets]]`timelion:target_buckets`:: +Used for calculating automatic intervals in visualizations, this is the number +of buckets to try to represent. [float] @@ -271,20 +446,32 @@ this is the number of buckets to try to represent. ==== Visualization [horizontal] -`visualization:colorMapping`:: Maps values to specified colors in visualizations. -`visualization:dimmingOpacity`:: The opacity of the chart items that are dimmed -when highlighting another element of the chart. The lower this number, the more -the highlighted element stands out. This must be a number between 0 and 1. -`visualization:loadingDelay`:: The time to wait before dimming visualizations -during a query. -`visualization:regionmap:showWarnings`:: Shows -a warning in a region map when terms cannot be joined to a shape. -`visualization:tileMap:WMSdefaults`:: The default properties for the WMS map server support in the coordinate map. -`visualization:tileMap:maxPrecision`:: The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, +[[visualization-colormapping]]`visualization:colorMapping`:: +Maps values to specified colors in visualizations. + +[[visualization-dimmingopacity]]`visualization:dimmingOpacity`:: +The opacity of the chart items that are dimmed when highlighting another element +of the chart. The lower this number, the more the highlighted element stands out. +This must be a number between 0 and 1. + +[[visualization-loadingdelay]]`visualization:loadingDelay`:: +The time to wait before dimming visualizations during a query. + +[[visualization-regionmap-showwarnings]]`visualization:regionmap:showWarnings`:: +Shows a warning in a region map when terms cannot be joined to a shape. + +[[visualization-tilemap-wmsdefaults]]`visualization:tileMap:WMSdefaults`:: +The default properties for the WMS map server support in the coordinate map. + +[[visualization-tilemap-maxprecision]]`visualization:tileMap:maxPrecision`:: +The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, and 12 is the maximum. See this {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[explanation of cell dimensions]. -`visualize:enableLabs`:: Enables users to create, view, and edit experimental visualizations. -If disabled, only visualizations that are considered production-ready are available to the user. + +[[visualize-enablelabs]]`visualize:enableLabs`:: +Enables users to create, view, and edit experimental visualizations. If disabled, +only visualizations that are considered production-ready are available to the +user. [float] diff --git a/docs/management/images/add_remote_cluster.png b/docs/management/images/add_remote_cluster.png deleted file mode 100755 index 160d29b741c62..0000000000000 Binary files a/docs/management/images/add_remote_cluster.png and /dev/null differ diff --git a/docs/management/images/auto_follow_pattern.png b/docs/management/images/auto_follow_pattern.png deleted file mode 100755 index f80de9352280f..0000000000000 Binary files a/docs/management/images/auto_follow_pattern.png and /dev/null differ diff --git a/docs/management/images/cross-cluster-replication-list-view.png b/docs/management/images/cross-cluster-replication-list-view.png deleted file mode 100755 index 4c45174cff7f1..0000000000000 Binary files a/docs/management/images/cross-cluster-replication-list-view.png and /dev/null differ diff --git a/docs/management/images/remote-clusters-list-view.png b/docs/management/images/remote-clusters-list-view.png deleted file mode 100755 index c28379863b74b..0000000000000 Binary files a/docs/management/images/remote-clusters-list-view.png and /dev/null differ diff --git a/docs/management/managing-ccr.asciidoc b/docs/management/managing-ccr.asciidoc deleted file mode 100644 index 9c06e479e28b2..0000000000000 --- a/docs/management/managing-ccr.asciidoc +++ /dev/null @@ -1,80 +0,0 @@ -[role="xpack"] -[[managing-cross-cluster-replication]] -== Cross-Cluster Replication - -Use *Cross-Cluster Replication* to reproduce indices in -remote clusters on a local cluster. {ref}/xpack-ccr.html[Cross-cluster replication] -is commonly used to provide remote backups for disaster recovery and for -geo-proximite copies of data. - -To get started, open the menu, then go to *Stack Management > Data > Cross-Cluster Replication*. - -[role="screenshot"] -image::images/cross-cluster-replication-list-view.png[][Cross-cluster replication list view] - -[float] -=== Prerequisites - -* You must have a {ref}/modules-remote-clusters.html[remote cluster]. -* Leader indices must meet {ref}/ccr-requirements.html[these requirements]. -* The Elasticsearch version of the local cluster must be the same as or newer than the remote cluster. -Refer to {ref}/ccr-overview.html[this document] for more information. - -[float] -=== Required permissions - -The `manage` and `manage_ccr` cluster privileges are required to access *Cross-Cluster Replication*. - -You can add these privileges in *Stack Management > Security > Roles*. - -[float] -[[configure-replication]] -=== Configure replication - -Replication requires a leader index, the index being replicated, and a -follower index, which will contain the leader index's replicated data. -The follower index is passive in that it can read requests and searches, -but cannot accept direct writes. Only the leader index is active for direct writes. - -You can configure follower indices in two ways: - -* Create specific follower indices -* Create follower indices from an auto-follow pattern - -[float] -==== Create specific follower indices - -To replicate data from existing indices, or set up local followers on a case-by-case basis, -go to *Follower indices*. When you create the follower index, you must reference the -remote cluster and the leader index that you created in the remote cluster. - -[role="screenshot"] -image::images/follower_indices.png[][UI for adding follower indices] - -[float] -==== Create follower indices from an auto-follow pattern - -To automatically detect and follow new indices when they are created on a remote cluster, -go to *Auto-follow patterns*. Creating an auto-follow pattern is useful when you have -time series data, like event logs, on the remote cluster that is created or rolled over on a daily basis. - -When creating the pattern, you must reference the remote cluster that you -connected to your local cluster. You must also specify a collection of index patterns -that match the indices you want to automatically follow. - -Once you configure an -auto-follow pattern, any time a new index with a name that matches the pattern is -created in the remote cluster, a follower index is automatically configured in the local cluster. - -[role="screenshot"] -image::images/auto_follow_pattern.png[UI for adding an auto-follow pattern] - -[float] -[[manage-replication]] -=== Manage replication - -Use the list views in *Cross-Cluster Replication* to monitor whether the replication is active and -pause and resume replication. You can also edit and remove the follower indices and auto-follow patterns. - -For an example of cross-cluster replication, -refer to https://www.elastic.co/blog/bi-directional-replication-with-elasticsearch-cross-cluster-replication-ccr[Bi-directional replication with Elasticsearch cross-cluster replication]. diff --git a/docs/management/managing-remote-clusters.asciidoc b/docs/management/managing-remote-clusters.asciidoc deleted file mode 100644 index 92e0fa822b056..0000000000000 --- a/docs/management/managing-remote-clusters.asciidoc +++ /dev/null @@ -1,50 +0,0 @@ -[[working-remote-clusters]] -== Remote Clusters - -Use *Remote Clusters* to establish a unidirectional -connection from your cluster to other clusters. This functionality is -required for {ref}/xpack-ccr.html[cross-cluster replication] and -{ref}/modules-cross-cluster-search.html[cross-cluster search]. - -To get started, open the menu, then go to *Stack Management > Data > Remote Clusters*. - -[role="screenshot"] -image::images/remote-clusters-list-view.png[Remote Clusters list view, including Add a remote cluster button] - -[float] -=== Required permissions - -The `manage` cluster privilege is required to access *Remote Clusters*. - -You can add this privilege in *Stack Management > Security > Roles*. - -[float] -[[managing-remote-clusters]] -=== Add a remote cluster - -A {ref}/modules-remote-clusters.html[remote cluster] connection works by configuring a remote cluster and -connecting to a limited number of nodes, called {ref}/modules-remote-clusters.html#sniff-mode[seed nodes], -in that cluster. -Alternatively, you can define a single proxy address for the remote cluster. - -By default, a cross-cluster request, such as a cross-cluster search or -replication request, fails if any cluster in the request is unavailable. -To skip a cluster when its unavailable, -set *Skip if unavailable* to true. - -Once you add a remote cluster, you can configure <> -to reproduce indices in the remote cluster on a local cluster. - -[role="screenshot"] -image::images/add_remote_cluster.png[][UI for adding a remote cluster] - -To create an index pattern to search across clusters, -use the same syntax that you’d use in a raw cross-cluster search request in {es}: :. -See <> for examples. - -[float] -[[manage-remote-clusters]] -=== Manage remote clusters - -From the *Remote Clusters* list view, you can drill down into each cluster and -view its status. You can also edit and delete a cluster. diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 42d1d89145d79..5067bc08bec99 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -100,3 +100,15 @@ This content has moved to the <> page. == TSVB This page was deleted. See <>. + +[role="exclude",id="managing-cross-cluster-replication"] +== Cross-Cluster Replication + +This content has moved. See +{ref}/ccr-getting-started.html[Set up cross-cluster replication]. + +[role="exclude",id="working-remote-clusters"] +== Remote clusters + +This content has moved. See +{ref}/ccr-getting-started.html#ccr-getting-started-remote-cluster[Connect to a remote cluster]. diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 6c8632efa9cc0..917821ad09e2f 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -33,7 +33,7 @@ For more information, see |=== | `monitoring.enabled` | Set to `true` (default) to enable the {monitor-features} in {kib}. Unlike the - `monitoring.ui.enabled` setting, when this setting is `false`, the + <> setting, when this setting is `false`, the monitoring back-end does not run and {kib} stats are not sent to the monitoring cluster. @@ -44,7 +44,7 @@ a|`monitoring.cluster_alerts.` | `monitoring.ui.elasticsearch.hosts` | Specifies the location of the {es} cluster where your monitoring data is stored. - By default, this is the same as `elasticsearch.hosts`. This setting enables + By default, this is the same as <>. This setting enables you to use a single {kib} instance to search and visualize data in your production cluster as well as monitor data sent to a dedicated monitoring cluster. @@ -58,7 +58,7 @@ a|`monitoring.cluster_alerts.` cluster uses the authenticated user's credentials, which must be the same on both the {es} monitoring cluster and the {es} production cluster. + + - If not set, {kib} uses the value of the `elasticsearch.username` setting. + If not set, {kib} uses the value of the <> setting. | `monitoring.ui.elasticsearch.password` | Specifies the password used by {kib} monitoring to establish a persistent connection @@ -69,11 +69,11 @@ a|`monitoring.cluster_alerts.` cluster uses the authenticated user's credentials, which must be the same on both the {es} monitoring cluster and the {es} production cluster. + + - If not set, {kib} uses the value of the `elasticsearch.password` setting. + If not set, {kib} uses the value of the <> setting. | `monitoring.ui.elasticsearch.pingTimeout` | Specifies the time in milliseconds to wait for {es} to respond to internal - health checks. By default, it matches the `elasticsearch.pingTimeout` setting, + health checks. By default, it matches the <> setting, which has a default value of `30000`. |=== @@ -112,7 +112,7 @@ about configuring {kib}, see | Specifies the number of log entries to display in *{stack-monitor-app}*. Defaults to `10`. The maximum value is `50`. -| `monitoring.ui.enabled` +|[[monitoring-ui-enabled]] `monitoring.ui.enabled` | Set to `false` to hide *{stack-monitor-app}*. The monitoring back-end continues to run as an agent for sending {kib} stats to the monitoring cluster. Defaults to `true`. diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index 3489dcd018293..adfc3964d4204 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -20,7 +20,7 @@ You can configure `xpack.reporting` settings in your `kibana.yml` to: | [[xpack-enable-reporting]]`xpack.reporting.enabled` {ess-icon} | Set to `false` to disable the {report-features}. -| `xpack.reporting.encryptionKey` {ess-icon} +|[[xpack-reporting-encryptionKey]] `xpack.reporting.encryptionKey` {ess-icon} | Set to an alphanumeric, at least 32 characters long text string. By default, {kib} will generate a random key when it starts, which will cause pending reports to fail after restart. Configure this setting to preserve the same key across multiple restarts and multiple instances of {kib}. @@ -53,20 +53,20 @@ proxy host requires that the {kib} server has network access to the proxy. [cols="2*<"] |=== | `xpack.reporting.kibanaServer.port` - | The port for accessing {kib}, if different from the `server.port` value. + | The port for accessing {kib}, if different from the <> value. | `xpack.reporting.kibanaServer.protocol` | The protocol for accessing {kib}, typically `http` or `https`. -| `xpack.reporting.kibanaServer.hostname` - | The hostname for accessing {kib}, if different from the `server.host` value. +|[[xpack-kibanaServer-hostname]] `xpack.reporting.kibanaServer.hostname` + | The hostname for accessing {kib}, if different from the <> value. |=== [NOTE] ============ Reporting authenticates requests on the Kibana page only when the hostname matches the -`xpack.reporting.kibanaServer.hostname` setting. Therefore Reporting would fail if the +<> setting. Therefore Reporting would fail if the set value redirects to another server. For that reason, `"0"` is an invalid setting because, in the Reporting browser, it becomes an automatic redirect to `"0.0.0.0"`. ============ @@ -97,8 +97,8 @@ reports, you might need to change the following settings. [NOTE] ============ Running multiple instances of {kib} in a cluster for load balancing of -reporting requires identical values for `xpack.reporting.encryptionKey` and, if -security is enabled, `xpack.security.encryptionKey`. +reporting requires identical values for <> and, if +security is enabled, <>. ============ [cols="2*<"] @@ -177,7 +177,7 @@ available, but there will likely be errors in the visualizations in the report. [[reporting-chromium-settings]] ==== Chromium settings -When `xpack.reporting.capture.browser.type` is set to `chromium` (default) you can also specify the following settings. +When <> is set to `chromium` (default) you can also specify the following settings. [cols="2*<"] |=== @@ -229,10 +229,10 @@ a| `xpack.reporting.capture.browser` See OWASP: https://www.owasp.org/index.php/CSV_Injection Defaults to `true`. -| `xpack.reporting.csv.enablePanelActionDownload` - | Enables CSV export from a saved search on a dashboard. This action is available in the dashboard - panel menu for the saved search. - Defaults to `true`. +| `xpack.reporting.csv` `.enablePanelActionDownload` + | Enables CSV export from a saved search on a dashboard. This action is available in the dashboard panel menu for the saved search. + *Note:* This setting exists for backwards compatibility, but is unused and hardcoded to `true`. CSV export from a saved search on a dashboard + is enabled when Reporting is enabled. |=== @@ -246,7 +246,7 @@ a| `xpack.reporting.capture.browser` | Reporting uses a weekly index in {es} to store the reporting job and the report content. The index is automatically created if it does not already exist. Configure this to a unique value, beginning with `.reporting-`, for every - {kib} instance that has a unique `kibana.index` setting. Defaults to `.reporting`. + {kib} instance that has a unique <> setting. Defaults to `.reporting`. | `xpack.reporting.roles.allow` | Specifies the roles in addition to superusers that can use reporting. diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index b6eecc6ea9f04..00e5f973f7d87 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -190,26 +190,26 @@ You can configure the following settings in the `kibana.yml` file. | `xpack.security.cookieName` | Sets the name of the cookie used for the session. The default value is `"sid"`. -| `xpack.security.encryptionKey` +|[[xpack-security-encryptionKey]] `xpack.security.encryptionKey` | An arbitrary string of 32 characters or more that is used to encrypt session information. Do **not** expose this key to users of {kib}. By default, a value is automatically generated in memory. If you use that default behavior, all sessions are invalidated when {kib} restarts. In addition, high-availability deployments of {kib} will behave unexpectedly if this setting isn't the same for all instances of {kib}. -| `xpack.security.secureCookies` +|[[xpack-security-secureCookies]] `xpack.security.secureCookies` | Sets the `secure` flag of the session cookie. The default value is `false`. It - is automatically set to `true` if `server.ssl.enabled` is set to `true`. Set + is automatically set to `true` if <> is set to `true`. Set this to `true` if SSL is configured outside of {kib} (for example, you are routing requests through a load balancer or proxy). | `xpack.security.sameSiteCookies` {ess-icon} | Sets the `SameSite` attribute of the session cookie. This allows you to declare whether your cookie should be restricted to a first-party or same-site context. Valid values are `Strict`, `Lax`, `None`. - This is *not set* by default, which modern browsers will treat as `Lax`. If you use Kibana embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting `xpack.security.secureCookies: true`. + This is *not set* by default, which modern browsers will treat as `Lax`. If you use Kibana embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting <>: `true`. -| `xpack.security.session.idleTimeout` {ess-icon} - | Ensures that user sessions will expire after a period of inactivity. This and `xpack.security.session.lifespan` are both +|[[xpack-session-idleTimeout]] `xpack.security.session.idleTimeout` {ess-icon} + | Ensures that user sessions will expire after a period of inactivity. This and <> are both highly recommended. By default, this setting is not set. 2+a| @@ -218,9 +218,9 @@ highly recommended. By default, this setting is not set. The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). ============ -| `xpack.security.session.lifespan` {ess-icon} +|[[xpack-session-lifespan]] `xpack.security.session.lifespan` {ess-icon} | Ensures that user sessions will expire after the defined time period. This behavior also known as an "absolute timeout". If -this is _not_ set, user sessions could stay active indefinitely. This and `xpack.security.session.idleTimeout` are both highly +this is _not_ set, user sessions could stay active indefinitely. This and <> are both highly recommended. By default, this setting is not set. 2+a| diff --git a/docs/settings/telemetry-settings.asciidoc b/docs/settings/telemetry-settings.asciidoc index 89c018a86eca6..0329e2f010e80 100644 --- a/docs/settings/telemetry-settings.asciidoc +++ b/docs/settings/telemetry-settings.asciidoc @@ -19,7 +19,7 @@ See our https://www.elastic.co/legal/privacy-statement[Privacy Statement] to lea [cols="2*<"] |=== -| `telemetry.enabled` +|[[telemetry-enabled]] `telemetry.enabled` | Set to `true` to send cluster statistics to Elastic. Reporting your cluster statistics helps us improve your user experience. Your data is never shared with anyone. Set to `false` to disable statistics reporting from any @@ -31,16 +31,16 @@ See our https://www.elastic.co/legal/privacy-statement[Privacy Statement] to lea it is behind a firewall and falls back to `'browser'` to send it from users' browsers when they are navigating through {kib}. Defaults to `'server'`. -| `telemetry.optIn` +|[[telemetry-optIn]] `telemetry.optIn` | Set to `true` to automatically opt into reporting cluster statistics. You can also opt out through *Advanced Settings* in {kib}. Defaults to `true`. | `telemetry.allowChangingOptInStatus` - | Set to `true` to allow overwriting the `telemetry.optIn` setting via the {kib} UI. Defaults to `true`. + + | Set to `true` to allow overwriting the <> setting via the {kib} UI. Defaults to `true`. + |=== [NOTE] ============ -When `false`, `telemetry.optIn` must be `true`. To disable telemetry and not allow users to change that parameter, use `telemetry.enabled`. +When `false`, <> must be `true`. To disable telemetry and not allow users to change that parameter, use <>. ============ diff --git a/docs/setup/secure-settings.asciidoc b/docs/setup/secure-settings.asciidoc index 10380eb5d8fa4..840a18ac03bab 100644 --- a/docs/setup/secure-settings.asciidoc +++ b/docs/setup/secure-settings.asciidoc @@ -19,7 +19,7 @@ bin/kibana-keystore create ---------------------------------------------------------------- The file `kibana.keystore` will be created in the directory defined by the -`path.data` configuration setting. +<> configuration setting. [float] [[list-settings]] diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index f03022e9e9f00..af68f3e541628 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -20,11 +20,11 @@ which may cause a delay before pages start being served. Set to `false` to disable Console. *Default: `true`* | `cpu.cgroup.path.override:` - | *deprecated* This setting has been renamed to `ops.cGroupOverrides.cpuPath` + | *deprecated* This setting has been renamed to <> and the old name will no longer be supported as of 8.0. | `cpuacct.cgroup.path.override:` - | *deprecated* This setting has been renamed to `ops.cGroupOverrides.cpuAcctPath` + | *deprecated* This setting has been renamed to <> and the old name will no longer be supported as of 8.0. | `csp.rules:` @@ -33,7 +33,7 @@ that disables certain unnecessary and potentially insecure capabilities in the browser. It is strongly recommended that you keep the default CSP rules that ship with {kib}. -| `csp.strict:` +|[[csp-strict]] `csp.strict:` | Blocks {kib} access to any browser that does not enforce even rudimentary CSP rules. In practice, this disables support for older, less safe browsers like Internet Explorer. @@ -43,44 +43,44 @@ For more information, refer to <>. | `csp.warnLegacyBrowsers:` | Shows a warning message after loading {kib} to any browser that does not enforce even rudimentary CSP rules, though {kib} is still accessible. This -configuration is effectively ignored when `csp.strict` is enabled. +configuration is effectively ignored when <> is enabled. *Default: `true`* | `elasticsearch.customHeaders:` | Header names and values to send to {es}. Any custom headers cannot be overwritten by client-side headers, regardless of the -`elasticsearch.requestHeadersWhitelist` configuration. *Default: `{}`* +<> configuration. *Default: `{}`* -| `elasticsearch.hosts:` +|[[elasticsearch-hosts]] `elasticsearch.hosts:` | The URLs of the {es} instances to use for all your queries. All nodes listed here must be on the same cluster. *Default: `[ "http://localhost:9200" ]`* -+ + To enable SSL/TLS for outbound connections to {es}, use the `https` protocol in this setting. | `elasticsearch.logQueries:` - | Log queries sent to {es}. Requires `logging.verbose` set to `true`. + | Log queries sent to {es}. Requires <> set to `true`. This is useful for seeing the query DSL generated by applications that currently do not have an inspector, for example Timelion and Monitoring. *Default: `false`* -| `elasticsearch.pingTimeout:` +|[[elasticsearch-pingTimeout]] `elasticsearch.pingTimeout:` | Time in milliseconds to wait for {es} to respond to pings. -*Default: the value of the `elasticsearch.requestTimeout` setting* +*Default: the value of the <> setting* | `elasticsearch.preserveHost:` | When the value is `true`, {kib} uses the hostname specified in the -`server.host` setting. When the value is `false`, {kib} uses +<> setting. When the value is `false`, {kib} uses the hostname of the host that connects to this {kib} instance. *Default: `true`* -| `elasticsearch.requestHeadersWhitelist:` +|[[elasticsearch-requestHeadersWhitelist]] `elasticsearch.requestHeadersWhitelist:` | List of {kib} client-side headers to send to {es}. To send *no* client-side headers, set this value to [] (an empty list). Removing the `authorization` header from being whitelisted means that you cannot use <> in {kib}. *Default: `[ 'authorization' ]`* -| `elasticsearch.requestTimeout:` +|[[elasticsearch-requestTimeout]] `elasticsearch.requestTimeout:` | Time in milliseconds to wait for responses from the back end or {es}. This value must be a positive integer. *Default: `30000`* @@ -99,7 +99,7 @@ nodes. *Default: `false`* | Update the list of {es} nodes immediately following a connection fault. *Default: `false`* -| `elasticsearch.ssl.alwaysPresentCertificate:` +|[[elasticsearch-ssl-alwaysPresentCertificate]] `elasticsearch.ssl.alwaysPresentCertificate:` | Controls {kib} behavior in regard to presenting a client certificate when requested by {es}. This setting applies to all outbound SSL/TLS connections to {es}, including requests that are proxied for end users. *Default: `false`* @@ -109,7 +109,7 @@ to {es}, including requests that are proxied for end users. *Default: `false`* [WARNING] ============ When {es} uses certificates to authenticate end users with a PKI realm -and `elasticsearch.ssl.alwaysPresentCertificate` is `true`, +and <> is `true`, proxied requests may be executed as the identity that is tied to the {kib} server. ============ @@ -117,7 +117,7 @@ server. [cols="2*<"] |=== -| `elasticsearch.ssl.certificate:` and `elasticsearch.ssl.key:` +|[[elasticsearch-ssl-cert-key]] `elasticsearch.ssl.certificate:` and `elasticsearch.ssl.key:` | Paths to a PEM-encoded X.509 client certificate and its corresponding private key. These are used by {kib} to authenticate itself when making outbound SSL/TLS connections to {es}. For this setting to take effect, the @@ -129,46 +129,48 @@ be set to `"required"` or `"optional"` to request a client certificate from [NOTE] ============ -These settings cannot be used in conjunction with `elasticsearch.ssl.keystore.path`. +These settings cannot be used in conjunction with +<>. ============ [cols="2*<"] |=== -| `elasticsearch.ssl.certificateAuthorities:` +|[[elasticsearch-ssl-certificateAuthorities]] `elasticsearch.ssl.certificateAuthorities:` | Paths to one or more PEM-encoded X.509 certificate authority (CA) certificates, which make up a trusted certificate chain for {es}. This chain is used by {kib} to establish trust when making outbound SSL/TLS connections to {es}. -+ + In addition to this setting, trusted certificates may be specified via -`elasticsearch.ssl.keystore.path` and/or `elasticsearch.ssl.truststore.path`. +<> and/or +<>. | `elasticsearch.ssl.keyPassphrase:` | The password that decrypts the private key that is specified -via `elasticsearch.ssl.key`. This value is optional, as the key may not be +via <>. This value is optional, as the key may not be encrypted. -| `elasticsearch.ssl.keystore.path:` +|[[elasticsearch-ssl-keystore-path]] `elasticsearch.ssl.keystore.path:` | Path to a PKCS#12 keystore that contains an X.509 client certificate and it's corresponding private key. These are used by {kib} to authenticate itself when making outbound SSL/TLS connections to {es}. For this setting, you must also set the `xpack.security.http.ssl.client_authentication` setting in {es} to `"required"` or `"optional"` to request a client certificate from {kib}. -+ + If the keystore contains any additional certificates, they are used as a trusted certificate chain for {es}. This chain is used by {kib} to establish trust when making outbound SSL/TLS connections to {es}. In addition to this setting, trusted certificates may be specified via -`elasticsearch.ssl.certificateAuthorities` and/or -`elasticsearch.ssl.truststore.path`. +<> and/or +<>. |=== [NOTE] ============ This setting cannot be used in conjunction with -`elasticsearch.ssl.certificate` or `elasticsearch.ssl.key`. +<> or <>. ============ [cols="2*<"] @@ -176,24 +178,24 @@ This setting cannot be used in conjunction with | `elasticsearch.ssl.keystore.password:` | The password that decrypts the keystore specified via -`elasticsearch.ssl.keystore.path`. If the keystore has no password, leave this +<>. If the keystore has no password, leave this as blank. If the keystore has an empty password, set this to `""`. -| `elasticsearch.ssl.truststore.path:`:: +|[[elasticsearch-ssl-truststore-path]] `elasticsearch.ssl.truststore.path:` | Path to a PKCS#12 trust store that contains one or more X.509 certificate authority (CA) certificates, which make up a trusted certificate chain for {es}. This chain is used by {kib} to establish trust when making outbound SSL/TLS connections to {es}. -+ + In addition to this setting, trusted certificates may be specified via -`elasticsearch.ssl.certificateAuthorities` and/or -`elasticsearch.ssl.keystore.path`. +<> and/or +<>. |`elasticsearch.ssl.truststore.password:` | The password that decrypts the trust store specified via -`elasticsearch.ssl.truststore.path`. If the trust store has no password, -leave this as blank. If the trust store has an empty password, set this to `""`. +<>. If the trust store +has no password, leave this as blank. If the trust store has an empty password, set this to `""`. | `elasticsearch.ssl.verificationMode:` | Controls the verification of the server certificate that {kib} receives when @@ -206,7 +208,7 @@ verification entirely. *Default: `"full"`* | Time in milliseconds to wait for {es} at {kib} startup before retrying. *Default: `5000`* -| `elasticsearch.username:` and `elasticsearch.password:` +|[[elasticsearch-user-passwd]] `elasticsearch.username:` and `elasticsearch.password:` | If your {es} is protected with basic authentication, these settings provide the username and password that the {kib} server uses to perform maintenance on the {kib} index at startup. {kib} users still need to authenticate with @@ -220,7 +222,7 @@ on the {kib} index at startup. {kib} users still need to authenticate with Please use the `defaultRoute` advanced setting instead. The default application to load. *Default: `"home"`* -| `kibana.index:` +|[[kibana-index]] `kibana.index:` | {kib} uses an index in {es} to store saved searches, visualizations, and dashboards. {kib} creates a new index if the index doesn’t already exist. If you configure a custom index, the name must be lowercase, and conform to the @@ -236,7 +238,7 @@ This value must be a whole number greater than zero. *Default: `"1000"`* suggestions. This value must be a whole number greater than zero. *Default: `"100000"`* -| `logging.dest:` +|[[logging-dest]] `logging.dest:` | Enables you to specify a file where {kib} stores log output. *Default: `stdout`* @@ -244,7 +246,7 @@ suggestions. This value must be a whole number greater than zero. | Logs output as JSON. When set to `true`, the logs are formatted as JSON strings that include timestamp, log level, context, message text, and any other metadata that may be associated with the log message. -When `logging.dest.stdout` is set, and there is no interactive terminal ("TTY"), +When <> is set, and there is no interactive terminal ("TTY"), this setting defaults to `true`. *Default: `false`* | `logging.quiet:` @@ -271,7 +273,7 @@ The following example shows a valid logging rotate configuration: | `logging.rotate.enabled:` | experimental[] Set the value of this setting to `true` to -enable log rotation. If you do not have a `logging.dest` set that is different from `stdout` +enable log rotation. If you do not have a <> set that is different from `stdout` that feature would not take any effect. *Default: `false`* | `logging.rotate.everyBytes:` @@ -286,9 +288,9 @@ option has to be in the range of 2 to 1024 files. *Default: `7`* | `logging.rotate.pollingInterval:` | experimental[] The number of milliseconds for the polling strategy in case -the `logging.rotate.usePolling` is enabled. `logging.rotate.usePolling` must be in the 5000 to 3600000 millisecond range. *Default: `10000`* +the <> is enabled. `logging.rotate.usePolling` must be in the 5000 to 3600000 millisecond range. *Default: `10000`* -| `logging.rotate.usePolling:` +|[[logging-rotate-usePolling]] `logging.rotate.usePolling:` | experimental[] By default we try to understand the best way to monitoring the log file and warning about it. Please be aware there are some systems where watch api is not accurate. In those cases, in order to get the feature working, the `polling` method could be used enabling that option. *Default: `false`* @@ -298,24 +300,25 @@ the `polling` method could be used enabling that option. *Default: `false`* suppress all logging output. *Default: `false`* | `logging.timezone` - | Set to the canonical timezone ID -(for example, `America/Los_Angeles`) to log events using that timezone. For a -list of timezones, refer to https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. *Default: `UTC`* + | Set to the canonical time zone ID +(for example, `America/Los_Angeles`) to log events using that time zone. +For possible values, refer to +https://en.wikipedia.org/wiki/List_of_tz_database_time_zones[database time zones]. *Default: `UTC`* -| [[logging-verbose]] `logging.verbose:` {ece-icon} +| [[logging-verbose]] `logging.verbose:` {ess-icon} | Set to `true` to log all events, including system usage information and all requests. *Default: `false`* -| `map.includeElasticMapsService:` {ess-icon} +| [[regionmap-ES-map]] `map.includeElasticMapsService:` {ess-icon} | Set to `false` to disable connections to Elastic Maps Service. -When `includeElasticMapsService` is turned off, only the vector layers configured by `map.regionmap` -and the tile layer configured by `map.tilemap.url` are available in <>. *Default: `true`* +When `includeElasticMapsService` is turned off, only the vector layers configured by <> +and the tile layer configured by <> are available in <>. *Default: `true`* | `map.proxyElasticMapsServiceInMaps:` | Set to `true` to proxy all <> Elastic Maps Service requests through the {kib} server. *Default: `false`* -| [[regionmap-settings]] `map.regionmap:` {ess-icon} {ece-icon} +| [[regionmap-settings]] `map.regionmap:` {ess-icon} | Specifies additional vector layers for use in <> visualizations. Each layer object points to an external vector file that contains a geojson @@ -345,16 +348,10 @@ map.regionmap: [cols="2*<"] |=== -| [[regionmap-ES-map]] `map.includeElasticMapsService:` {ece-icon} - | Turns on or off whether layers from the Elastic Maps Service should be included in the vector -layer option list. By turning this off, -only the layers that are configured here will be included. The default is `true`. -This also affects whether tile-service from the Elastic Maps Service will be available. - -| [[regionmap-attribution]] `map.regionmap.layers[].attribution:` {ess-icon} {ece-icon} +| [[regionmap-attribution]] `map.regionmap.layers[].attribution:` {ess-icon} | Optional. References the originating source of the geojson file. -| [[regionmap-fields]] `map.regionmap.layers[].fields[]:` {ess-icon} {ece-icon} +| [[regionmap-fields]] `map.regionmap.layers[].fields[]:` {ess-icon} | Mandatory. Each layer can contain multiple fields to indicate what properties from the geojson features you wish to expose. The following shows how to define multiple @@ -380,11 +377,11 @@ map.regionmap: [cols="2*<"] |=== -| [[regionmap-field-description]] `map.regionmap.layers[].fields[].description:` {ess-icon} {ece-icon} +| [[regionmap-field-description]] `map.regionmap.layers[].fields[].description:` {ess-icon} | Mandatory. The human readable text that is shown under the Options tab when building the Region Map visualization. -| [[regionmap-field-name]] `map.regionmap.layers[].fields[].name:` {ess-icon} {ece-icon} +| [[regionmap-field-name]] `map.regionmap.layers[].fields[].name:` {ess-icon} | Mandatory. This value is used to do an inner-join between the document stored in {es} and the geojson file. For example, if the field in the geojson is @@ -392,30 +389,30 @@ called `Location` and has city names, there must be a field in {es} that holds the same values that {kib} can then use to lookup for the geoshape data. -| [[regionmap-name]] `map.regionmap.layers[].name:` {ess-icon} {ece-icon} +| [[regionmap-name]] `map.regionmap.layers[].name:` {ess-icon} | Mandatory. A description of the map being provided. -| [[regionmap-url]] `map.regionmap.layers[].url:` {ess-icon} {ece-icon} +| [[regionmap-url]] `map.regionmap.layers[].url:` {ess-icon} | Mandatory. The location of the geojson file as provided by a webserver. -| [[tilemap-settings]] `map.tilemap.options.attribution:` {ess-icon} {ece-icon} +| [[tilemap-settings]] `map.tilemap.options.attribution:` {ess-icon} | The map attribution string. *Default: `"© [Elastic Maps Service](https://www.elastic.co/elastic-maps-service)"`* -| [[tilemap-max-zoom]] `map.tilemap.options.maxZoom:` {ess-icon} {ece-icon} +| [[tilemap-max-zoom]] `map.tilemap.options.maxZoom:` {ess-icon} | The maximum zoom level. *Default: `10`* -| [[tilemap-min-zoom]] `map.tilemap.options.minZoom:` {ess-icon} {ece-icon} +| [[tilemap-min-zoom]] `map.tilemap.options.minZoom:` {ess-icon} | The minimum zoom level. *Default: `1`* -| [[tilemap-subdomains]] `map.tilemap.options.subdomains:` {ess-icon} {ece-icon} +| [[tilemap-subdomains]] `map.tilemap.options.subdomains:` {ess-icon} | An array of subdomains used by the tile service. Specify the position of the subdomain the URL with the token `{s}`. -| [[tilemap-url]] `map.tilemap.url:` {ess-icon} {ece-icon} +| [[tilemap-url]] `map.tilemap.url:` {ess-icon} | The URL to the tileservice that {kib} uses to display map tiles in tilemap visualizations. By default, {kib} reads this URL from an external metadata service, but users can @@ -427,7 +424,7 @@ override this parameter to use their own Tile Map Service. For example: system for the {kib} UI notification center. Set to `false` to disable the newsfeed system. *Default: `true`* -| `path.data:` +|[[path-data]] `path.data:` | The path where {kib} stores persistent data not saved in {es}. *Default: `data`* @@ -438,17 +435,17 @@ not saved in {es}. *Default: `data`* | Set the interval in milliseconds to sample system and process performance metrics. The minimum value is 100. *Default: `5000`* -| `ops.cGroupOverrides.cpuPath:` +|[[ops-cGroupOverrides-cpuPath]] `ops.cGroupOverrides.cpuPath:` | Override for cgroup cpu path when mounted in a manner that is inconsistent with `/proc/self/cgroup`. -| `ops.cGroupOverrides.cpuAcctPath:` +|[[ops-cGroupOverrides-cpuAcctPath]] `ops.cGroupOverrides.cpuAcctPath:` | Override for cgroup cpuacct path when mounted in a manner that is inconsistent with `/proc/self/cgroup`. -| `server.basePath:` +|[[server-basePath]] `server.basePath:` | Enables you to specify a path to mount {kib} at if you are -running behind a proxy. Use the `server.rewriteBasePath` setting to tell {kib} +running behind a proxy. Use the <> setting to tell {kib} if it should remove the basePath from requests it receives, and to prevent a deprecation warning at startup. This setting cannot end in a slash (`/`). @@ -458,19 +455,19 @@ deprecation warning at startup. This setting cannot end in a slash (`/`). | `server.compression.referrerWhitelist:` | Specifies an array of trusted hostnames, such as the {kib} host, or a reverse proxy sitting in front of it. This determines whether HTTP compression may be used for responses, based on the request `Referer` header. -This setting may not be used when `server.compression.enabled` is set to `false`. *Default: `none`* +This setting may not be used when <> is set to `false`. *Default: `none`* | `server.customResponseHeaders:` {ess-icon} | Header names and values to send on all responses to the client from the {kib} server. *Default: `{}`* -| `server.host:` +|[[server-host]] `server.host:` | This setting specifies the host of the back end server. To allow remote users to connect, set the value to the IP address or DNS name of the {kib} server. *Default: `"localhost"`* | `server.keepaliveTimeout:` | The number of milliseconds to wait for additional data before restarting -the `server.socketTimeout` counter. *Default: `"120000"`* +the <> counter. *Default: `"120000"`* | `server.maxPayloadBytes:` | The maximum payload size in bytes @@ -480,28 +477,28 @@ for incoming server requests. *Default: `1048576`* | A human-readable display name that identifies this {kib} instance. *Default: `"your-hostname"`* -| `server.port:` +|[[server-port]] `server.port:` | {kib} is served by a back end server. This setting specifies the port to use. *Default: `5601`* -| `server.requestId.allowFromAnyIp:` +|[[server-requestId-allowFromAnyIp]] `server.requestId.allowFromAnyIp:` | Sets whether or not the X-Opaque-Id header should be trusted from any IP address for identifying requests in logs and forwarded to Elasticsearch. | `server.requestId.ipAllowlist:` - | A list of IPv4 and IPv6 address which the `X-Opaque-Id` header should be trusted from. Normally this would be set to the IP addresses of the load balancers or reverse-proxy that end users use to access Kibana. If any are set, `server.requestId.allowFromAnyIp` must also be set to `false.` + | A list of IPv4 and IPv6 address which the `X-Opaque-Id` header should be trusted from. Normally this would be set to the IP addresses of the load balancers or reverse-proxy that end users use to access Kibana. If any are set, <> must also be set to `false.` -| `server.rewriteBasePath:` +|[[server-rewriteBasePath]] `server.rewriteBasePath:` | Specifies whether {kib} should -rewrite requests that are prefixed with `server.basePath` or require that they +rewrite requests that are prefixed with <> or require that they are rewritten by your reverse proxy. In {kib} 6.3 and earlier, the default is `false`. In {kib} 7.x, the setting is deprecated. In {kib} 8.0 and later, the default is `true`. *Default: `deprecated`* -| `server.socketTimeout:` +|[[server-socketTimeout]] `server.socketTimeout:` | The number of milliseconds to wait before closing an inactive socket. *Default: `"120000"`* -| `server.ssl.certificate:` and `server.ssl.key:` +|[[server-ssl-cert-key]] `server.ssl.certificate:` and `server.ssl.key:` | Paths to a PEM-encoded X.509 server certificate and its corresponding private key. These are used by {kib} to establish trust when receiving inbound SSL/TLS connections from users. @@ -509,18 +506,18 @@ are used by {kib} to establish trust when receiving inbound SSL/TLS connections [NOTE] ============ -These settings cannot be used in conjunction with `server.ssl.keystore.path`. +These settings cannot be used in conjunction with <>. ============ [cols="2*<"] |=== -| `server.ssl.certificateAuthorities:` +|[[server-ssl-certificateAuthorities]] `server.ssl.certificateAuthorities:` | Paths to one or more PEM-encoded X.509 certificate authority (CA) certificates which make up a trusted certificate chain for {kib}. This chain is used by {kib} to establish trust when receiving inbound SSL/TLS connections from end users. If PKI authentication is enabled, this chain is also used by {kib} to verify client certificates from end users. -+ -In addition to this setting, trusted certificates may be specified via `server.ssl.keystore.path` and/or `server.ssl.truststore.path`. + +In addition to this setting, trusted certificates may be specified via <> and/or <>. | `server.ssl.cipherSuites:` | Details on the format, and the valid options, are available via the @@ -533,53 +530,53 @@ connections. Valid values are `"required"`, `"optional"`, and `"none"`. Using `" client presents a certificate, using `"optional"` will allow a client to present a certificate if it has one, and using `"none"` will prevent a client from presenting a certificate. *Default: `"none"`* -| `server.ssl.enabled:` +|[[server-ssl-enabled]] `server.ssl.enabled:` | Enables SSL/TLS for inbound connections to {kib}. When set to `true`, a certificate and its -corresponding private key must be provided. These can be specified via `server.ssl.keystore.path` or the combination of -`server.ssl.certificate` and `server.ssl.key`. *Default: `false`* +corresponding private key must be provided. These can be specified via <> or the combination of +<> and <>. *Default: `false`* | `server.ssl.keyPassphrase:` - | The password that decrypts the private key that is specified via `server.ssl.key`. This value + | The password that decrypts the private key that is specified via <>. This value is optional, as the key may not be encrypted. -| `server.ssl.keystore.path:` +|[[server-ssl-keystore-path]] `server.ssl.keystore.path:` | Path to a PKCS#12 keystore that contains an X.509 server certificate and its corresponding private key. If the keystore contains any additional certificates, those will be used as a trusted certificate chain for {kib}. All of these are used by {kib} to establish trust when receiving inbound SSL/TLS connections from end users. The certificate chain is also used by {kib} to verify client certificates from end users when PKI authentication is enabled. -+ -In addition to this setting, trusted certificates may be specified via `server.ssl.certificateAuthorities` and/or -`server.ssl.truststore.path`. + +In addition to this setting, trusted certificates may be specified via <> and/or +<>. |=== [NOTE] ============ -This setting cannot be used in conjunction with `server.ssl.certificate` or `server.ssl.key` +This setting cannot be used in conjunction with <> or <> ============ [cols="2*<"] |=== | `server.ssl.keystore.password:` - | The password that will be used to decrypt the keystore specified via `server.ssl.keystore.path`. If the + | The password that will be used to decrypt the keystore specified via <>. If the keystore has no password, leave this unset. If the keystore has an empty password, set this to `""`. -| `server.ssl.truststore.path:` +|[[server-ssl-truststore-path]] `server.ssl.truststore.path:` | Path to a PKCS#12 trust store that contains one or more X.509 certificate authority (CA) certificates which make up a trusted certificate chain for {kib}. This chain is used by {kib} to establish trust when receiving inbound SSL/TLS connections from end users. If PKI authentication is enabled, this chain is also used by {kib} to verify client certificates from end users. -+ -In addition to this setting, trusted certificates may be specified via `server.ssl.certificateAuthorities` and/or -`server.ssl.keystore.path`. + +In addition to this setting, trusted certificates may be specified via <> and/or +<>. | `server.ssl.truststore.password:` - | The password that will be used to decrypt the trust store specified via `server.ssl.truststore.path`. If + | The password that will be used to decrypt the trust store specified via <>. If the trust store has no password, leave this unset. If the trust store has an empty password, set this to `""`. | `server.ssl.redirectHttpFromPort:` | {kib} binds to this port and redirects -all http requests to https over the port configured as `server.port`. +all http requests to https over the port configured as <>. | `server.ssl.supportedProtocols:` | An array of supported protocols with versions. @@ -588,7 +585,7 @@ Valid protocols: `TLSv1`, `TLSv1.1`, `TLSv1.2`. *Default: TLSv1.1, TLSv1.2* | [[settings-xsrf-whitelist]] `server.xsrf.whitelist:` | It is not recommended to disable protections for arbitrary API endpoints. Instead, supply the `kbn-xsrf` header. -The `server.xsrf.whitelist` setting requires the following format: +The <> setting requires the following format: |=== @@ -608,18 +605,18 @@ The `server.xsrf.whitelist` setting requires the following format: setting this to `true` enables unauthenticated users to access the {kib} server status API and status page. *Default: `false`* -| `telemetry.allowChangingOptInStatus` +|[[telemetry-allowChangingOptInStatus]] `telemetry.allowChangingOptInStatus` | When `true`, users are able to change the telemetry setting at a later time in <>. When `false`, -{kib} looks at the value of `telemetry.optIn` to determine whether to send -telemetry data or not. `telemetry.allowChangingOptInStatus` and `telemetry.optIn` +{kib} looks at the value of <> to determine whether to send +telemetry data or not. <> and <> cannot be `false` at the same time. *Default: `true`*. -| `telemetry.optIn` +|[[settings-telemetry-optIn]] `telemetry.optIn` | When `true`, telemetry data is sent to Elastic. When `false`, collection of telemetry data is disabled. To enable telemetry and prevent users from disabling it, -set `telemetry.allowChangingOptInStatus` to `false` and `telemetry.optIn` to `true`. +set <> to `false` and <> to `true`. *Default: `true`* | `telemetry.enabled` diff --git a/docs/user/alerting/action-types.asciidoc b/docs/user/alerting/action-types.asciidoc index be31458ff39fa..af80b17f8605f 100644 --- a/docs/user/alerting/action-types.asciidoc +++ b/docs/user/alerting/action-types.asciidoc @@ -11,10 +11,19 @@ a| <> | Send email from your server. +a| <> + +| Create an incident in IBM Resilient. + a| <> | Index data into Elasticsearch. +a| <> + +| Create an incident in Jira. + + a| <> | Send an event in PagerDuty. @@ -53,10 +62,12 @@ before {kib} starts. If you preconfigure a connector, you can also <>. include::action-types/email.asciidoc[] +include::action-types/resilient.asciidoc[] include::action-types/index.asciidoc[] +include::action-types/jira.asciidoc[] include::action-types/pagerduty.asciidoc[] include::action-types/server-log.asciidoc[] +include::action-types/servicenow.asciidoc[] include::action-types/slack.asciidoc[] include::action-types/webhook.asciidoc[] include::action-types/pre-configured-connectors.asciidoc[] -include::action-types/servicenow.asciidoc[] diff --git a/docs/user/alerting/action-types/jira.asciidoc b/docs/user/alerting/action-types/jira.asciidoc new file mode 100644 index 0000000000000..48bd6c8501b9f --- /dev/null +++ b/docs/user/alerting/action-types/jira.asciidoc @@ -0,0 +1,77 @@ +[role="xpack"] +[[jira-action-type]] +=== Jira action + +The Jira action type uses the https://developer.atlassian.com/cloud/jira/platform/rest/v2/[REST API v2] to create Jira issues. + +[float] +[[jira-connector-configuration]] +==== Connector configuration + +Jira connectors have the following configuration properties: + +Name:: The name of the connector. The name is used to identify a connector in the **Stack Management** UI connector listing, and in the connector list when configuring an action. +URL:: Jira instance URL. +Project key:: Jira project key. +Email (or username):: The account email (or username) for HTTP Basic authentication. +API token (or password):: Jira API authentication token (or password) for HTTP Basic authentication. + +[float] +[[Preconfigured-jira-configuration]] +==== Preconfigured action type + +[source,text] +-- + my-jira: + name: preconfigured-jira-action-type + actionTypeId: .jira + config: + apiUrl: https://elastic.atlassian.net + projectKey: ES + secrets: + email: testuser + apiToken: tokenkeystorevalue +-- + +`config` defines the action type specific to the configuration and contains the following properties: + +[cols="2*<"] +|=== + +| `apiUrl` +| An address that corresponds to *URL*. + +| `projectKey` +| A key that corresponds to *Project Key*. + +|=== + +`secrets` defines sensitive information for the action type: + +[cols="2*<"] +|=== + +| `email` +| A string that corresponds to *Email*. + +| `apiToken` +| A string that corresponds to *API Token*. Should be stored in the <>. + +|=== + +[[jira-action-configuration]] +==== Action configuration + +Jira actions have the following configuration properties: + +Issue type:: The type of the issue. +Priority:: The priority of the incident. +Labels:: The labels of the incident. +Title:: A title for the issue, used for searching the contents of the knowledge base. +Description:: The details about the incident. +Additional comments:: Additional information for the client, such as how to troubleshoot the issue. + +[[configuring-jira]] +==== Configuring and testing Jira + +Jira offers free https://www.atlassian.com/software/jira/free[Instances], which you can use to test incidents. diff --git a/docs/user/alerting/action-types/pagerduty.asciidoc b/docs/user/alerting/action-types/pagerduty.asciidoc index 2c9add5233c91..9301224e6df48 100644 --- a/docs/user/alerting/action-types/pagerduty.asciidoc +++ b/docs/user/alerting/action-types/pagerduty.asciidoc @@ -36,7 +36,7 @@ This is required to encrypt parameters that must be secured, for example PagerDu If you have security enabled: * You must have -application privileges to access Metrics, APM, Uptime, or SIEM. +application privileges to access Metrics, APM, Uptime, or Security. * If you are using a self-managed deployment with security, you must have Transport Security Layer (TLS) enabled for communication <>. Alerts uses API keys to secure background alert checks and actions, diff --git a/docs/user/alerting/action-types/resilient.asciidoc b/docs/user/alerting/action-types/resilient.asciidoc new file mode 100644 index 0000000000000..b5ddb76d49b0c --- /dev/null +++ b/docs/user/alerting/action-types/resilient.asciidoc @@ -0,0 +1,76 @@ +[role="xpack"] +[[resilient-action-type]] +=== IBM Resilient action + +The IBM Resilient action type uses the https://developer.ibm.com/security/resilient/rest/[RESILIENT REST v2] to create IBM Resilient incidents. + +[float] +[[resilient-connector-configuration]] +==== Connector configuration + +IBM Resilient connectors have the following configuration properties: + +Name:: The name of the connector. The name is used to identify a connector in the **Stack Management** UI connector listing, and in the connector list when configuring an action. +URL:: IBM Resilient instance URL. +Organization ID:: IBM Resilient organization ID. +API key ID:: The authentication key ID for HTTP Basic authentication. +API key secret:: The authentication key secret for HTTP Basic authentication. + +[float] +[[Preconfigured-resilient-configuration]] +==== Preconfigured action type + +[source,text] +-- + my-resilient: + name: preconfigured-resilient-action-type + actionTypeId: .resilient + config: + apiUrl: https://elastic.resilient.net + orgId: ES + secrets: + apiKeyId: testuser + apiKeySecret: tokenkeystorevalue +-- + +`config` defines the action type specific to the configuration and contains the following properties: + +[cols="2*<"] +|=== + +| `apiUrl` +| An address that corresponds to *URL*. + +| `orgId` +| An ID that corresponds to *Organization ID*. + +|=== + +`secrets` defines sensitive information for the action type: + +[cols="2*<"] +|=== + +| `apiKeyId` +| A string that corresponds to *API key ID*. + +| `apiKeySecret` +| A string that corresponds to *API Key secret*. Should be stored in the <>. + +|=== + +[[resilient-action-configuration]] +==== Action configuration + +IBM Resilient actions have the following configuration properties: + +Incident types:: The incident types of the incident. +Severity code:: The severity of the incident. +Name:: A name for the issue, used for searching the contents of the knowledge base. +Description:: The details about the incident. +Additional comments:: Additional information for the client, such as how to troubleshoot the issue. + +[[configuring-resilient]] +==== Configuring and testing IBM Resilient + +IBM Resilient offers https://www.ibm.com/security/intelligent-orchestration/resilient[Instances], which you can use to test incidents. diff --git a/docs/user/alerting/action-types/servicenow.asciidoc b/docs/user/alerting/action-types/servicenow.asciidoc index 32f828aea2357..0acb92bcdb5ee 100644 --- a/docs/user/alerting/action-types/servicenow.asciidoc +++ b/docs/user/alerting/action-types/servicenow.asciidoc @@ -10,7 +10,7 @@ The ServiceNow action type uses the https://developer.servicenow.com/app.do#!/re ServiceNow connectors have the following configuration properties: -Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. +Name:: The name of the connector. The name is used to identify a connector in the **Stack Management** UI connector listing, and in the connector list when configuring an action. URL:: ServiceNow instance URL. Username:: Username for HTTP Basic authentication. Password:: Password for HTTP Basic authentication. @@ -37,7 +37,7 @@ Password:: Password for HTTP Basic authentication. |=== | `apiUrl` -| An address that corresponds to *Sender*. +| An address that corresponds to *URL*. |=== @@ -47,7 +47,7 @@ Password:: Password for HTTP Basic authentication. |=== | `username` -| A string that corresponds to *User*. +| A string that corresponds to *Username*. | `password` | A string that corresponds to *Password*. Should be stored in the <>. @@ -62,7 +62,7 @@ ServiceNow actions have the following configuration properties: Urgency:: The extent to which the incident resolution can delay. Severity:: The severity of the incident. Impact:: The effect an incident has on business. Can be measured by the number of affected users or by how critical it is to the business in question. -Short description:: A short description of the incident, used for searching the contents of the knowledge base. +Short description:: A short description for the incident, used for searching the contents of the knowledge base. Description:: The details about the incident. Additional comments:: Additional information for the client, such as how to troubleshoot the issue. diff --git a/docs/user/alerting/alerting-getting-started.asciidoc b/docs/user/alerting/alerting-getting-started.asciidoc index 6bc085b0f78b9..bdb72b1658cd2 100644 --- a/docs/user/alerting/alerting-getting-started.asciidoc +++ b/docs/user/alerting/alerting-getting-started.asciidoc @@ -6,7 +6,7 @@ beta[] -- -Alerting allows you to detect complex conditions within different {kib} apps and trigger actions when those conditions are met. Alerting is integrated with <>, <>, <>, <>, can be centrally managed from the <> UI, and provides a set of built-in <> and <> for you to use. +Alerting allows you to detect complex conditions within different {kib} apps and trigger actions when those conditions are met. Alerting is integrated with <>, <>, <>, <>, can be centrally managed from the <> UI, and provides a set of built-in <> and <> for you to use. image::images/alerting-overview.png[Alerts and actions UI] @@ -148,7 +148,7 @@ Functionally, {kib} alerting differs in that: * {kib} alerts tracks and persists the state of each detected condition through *alert instances*. This makes it possible to mute and throttle individual instances, and detect changes in state such as resolution. * Actions are linked to *alert instances* in {kib} alerting. Actions are fired for each occurrence of a detected condition, rather than for the entire alert. -At a higher level, {kib} alerts allow rich integrations across use cases like <>, <>, <>, and <>. +At a higher level, {kib} alerts allow rich integrations across use cases like <>, <>, <>, and <>. Pre-packaged *alert types* simplify setup, hide the details complex domain-specific detections, while providing a consistent interface across {kib}. [float] @@ -171,7 +171,7 @@ To access alerting in a space, a user must have access to one of the following f * <> * <> -* <> +* <> * <> See <> for more information on configuring roles that provide access to these features. diff --git a/docs/user/alerting/defining-alerts.asciidoc b/docs/user/alerting/defining-alerts.asciidoc index d05a727016455..7f201d2c39e89 100644 --- a/docs/user/alerting/defining-alerts.asciidoc +++ b/docs/user/alerting/defining-alerts.asciidoc @@ -2,7 +2,7 @@ [[defining-alerts]] == Defining alerts -{kib} alerts can be created in a variety of apps including <>, <>, <>, <> and from <> UI. While alerting details may differ from app to app, they share a common interface for defining and configuring alerts that this section describes in more detail. +{kib} alerts can be created in a variety of apps including <>, <>, <>, <> and from <> UI. While alerting details may differ from app to app, they share a common interface for defining and configuring alerts that this section describes in more detail. [float] === Alert flyout diff --git a/docs/user/dashboard/url-drilldown.asciidoc b/docs/user/dashboard/url-drilldown.asciidoc index e6daf89d72718..ee879256a1fae 100644 --- a/docs/user/dashboard/url-drilldown.asciidoc +++ b/docs/user/dashboard/url-drilldown.asciidoc @@ -238,3 +238,14 @@ Tip: Consider using <> helper for date formatting. | Aggregation field behind the selected range, if available. |=== + +[float] +[[disable]] +==== Disable URL drilldown + +You can disable URL drilldown feature on your {kib} instance by disabling the plugin: + +["source","yml"] +----------- +url_drilldown.enabled: false +----------- diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index bc96463f6efba..e0d550a15a907 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -58,12 +58,12 @@ years of historical data in combination with your raw data. | {ref}/transforms.html[Transforms] |Use transforms to pivot existing {es} indices into summarized or entity-centric indices. -| <> +| {ref}/ccr-getting-started.html[Cross-Cluster Replication] |Replicate indices on a remote cluster and copy them to a follower index on a local cluster. This is important for disaster recovery. It also keeps data local for faster queries. -| <> +| {ref}/ccr-getting-started.html#ccr-getting-started-remote-cluster[Remote Clusters] |Manage your remote clusters for use with cross-cluster search and cross-cluster replication. You can add and remove remote clusters, and check their connectivity. |=== @@ -180,8 +180,6 @@ include::{kib-repo-dir}/management/alerting/connector-management.asciidoc[] include::{kib-repo-dir}/management/managing-beats.asciidoc[] -include::{kib-repo-dir}/management/managing-ccr.asciidoc[] - include::{kib-repo-dir}/management/index-lifecycle-policies/intro-to-lifecycle-policies.asciidoc[] include::{kib-repo-dir}/management/index-lifecycle-policies/create-policy.asciidoc[] @@ -200,8 +198,6 @@ include::{kib-repo-dir}/management/managing-licenses.asciidoc[] include::{kib-repo-dir}/management/numeral.asciidoc[] -include::{kib-repo-dir}/management/managing-remote-clusters.asciidoc[] - include::{kib-repo-dir}/management/rollups/create_and_manage_rollups.asciidoc[] include::{kib-repo-dir}/management/managing-saved-objects.asciidoc[] diff --git a/examples/search_examples/public/components/app.tsx b/examples/search_examples/public/components/app.tsx index 704d31d42e640..ab0ce185f0602 100644 --- a/examples/search_examples/public/components/app.tsx +++ b/examples/search_examples/public/components/app.tsx @@ -56,6 +56,8 @@ import { IndexPatternSelect, IndexPattern, IndexPatternField, + isCompleteResponse, + isErrorResponse, } from '../../../../src/plugins/data/public'; interface SearchExamplesAppDeps { @@ -144,7 +146,7 @@ export const SearchExamplesApp = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { setTimeTook(response.rawResponse.took); const avgResult: number | undefined = response.rawResponse.aggregations ? response.rawResponse.aggregations[1].value @@ -162,7 +164,7 @@ export const SearchExamplesApp = ({ text: mountReactNode(message), }); searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { // TODO: Make response error status clearer notifications.toasts.addWarning('An error has occurred'); searchSubscription$.unsubscribe(); diff --git a/examples/search_examples/server/my_strategy.ts b/examples/search_examples/server/my_strategy.ts index a1116ddbd759b..1f59d0a5d8f3a 100644 --- a/examples/search_examples/server/my_strategy.ts +++ b/examples/search_examples/server/my_strategy.ts @@ -20,15 +20,16 @@ import { ISearchStrategy, PluginStart } from '../../../src/plugins/data/server'; import { IMyStrategyResponse, IMyStrategyRequest } from '../common'; -export const mySearchStrategyProvider = (data: PluginStart): ISearchStrategy => { +export const mySearchStrategyProvider = ( + data: PluginStart +): ISearchStrategy => { const es = data.search.getSearchStrategy('es'); return { - search: async (context, request, options): Promise => { - request.debug = true; + search: async (context, request, options) => { const esSearchRes = await es.search(context, request, options); return { ...esSearchRes, - cool: (request as IMyStrategyRequest).get_cool ? 'YES' : 'NOPE', + cool: request.get_cool ? 'YES' : 'NOPE', }; }, cancel: async (context, id) => { diff --git a/kibana.d.ts b/kibana.d.ts index b707405ffbeaf..50f8b8690d944 100644 --- a/kibana.d.ts +++ b/kibana.d.ts @@ -28,7 +28,6 @@ export { Public, Server }; /** * All exports from TS ambient definitions (where types are added for JS source in a .d.ts file). */ -import * as LegacyKibanaPluginSpec from './src/legacy/plugin_discovery/plugin_spec/plugin_spec_options'; import * as LegacyKibanaServer from './src/legacy/server/kbn_server'; /** @@ -39,8 +38,4 @@ export namespace Legacy { export type Request = LegacyKibanaServer.Request; export type ResponseToolkit = LegacyKibanaServer.ResponseToolkit; export type Server = LegacyKibanaServer.Server; - - export type InitPluginFunction = LegacyKibanaPluginSpec.InitPluginFunction; - export type UiExports = LegacyKibanaPluginSpec.UiExports; - export type PluginSpecOptions = LegacyKibanaPluginSpec.PluginSpecOptions; } diff --git a/package.json b/package.json index 57f5ac16059c9..10083ff07fccd 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "@babel/core": "^7.11.1", "@babel/register": "^7.10.5", "@elastic/datemath": "5.0.3", - "@elastic/elasticsearch": "7.9.0-rc.2", + "@elastic/elasticsearch": "7.9.1", "@elastic/eui": "29.0.0", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "^2.5.0", @@ -138,6 +138,8 @@ "@kbn/telemetry-tools": "1.0.0", "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", + "@kbn/ace": "1.0.0", + "@kbn/monaco": "1.0.0", "@kbn/ui-shared-deps": "1.0.0", "@types/yauzl": "^2.9.1", "JSONStream": "1.3.5", @@ -193,7 +195,7 @@ "p-map": "^4.0.0", "pegjs": "0.10.0", "proxy-from-env": "1.0.0", - "query-string": "5.1.1", + "query-string": "^6.13.2", "re2": "^1.15.4", "react": "^16.12.0", "react-color": "^2.13.8", @@ -225,9 +227,9 @@ "devDependencies": { "@babel/parser": "^7.11.2", "@babel/types": "^7.11.0", - "@elastic/apm-rum": "^5.5.0", + "@elastic/apm-rum": "^5.6.0", "@elastic/charts": "21.1.2", - "@elastic/ems-client": "7.9.3", + "@elastic/ems-client": "7.10.0", "@elastic/eslint-config-kibana": "0.15.0", "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/filesaver": "1.1.2", @@ -248,8 +250,11 @@ "@microsoft/api-documenter": "7.7.2", "@microsoft/api-extractor": "7.7.0", "@percy/agent": "^0.26.0", - "@testing-library/react": "^9.3.2", - "@testing-library/react-hooks": "^3.2.1", + "@testing-library/dom": "^7.24.2", + "@testing-library/jest-dom": "^5.11.4", + "@testing-library/react": "^11.0.4", + "@testing-library/react-hooks": "^3.4.1", + "@testing-library/user-event": "^12.1.6", "@types/accept": "3.1.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", @@ -329,10 +334,8 @@ "@types/supertest": "^2.0.5", "@types/supertest-as-promised": "^2.0.38", "@types/tar": "^4.0.3", - "@types/testing-library__dom": "^6.10.0", - "@types/testing-library__jest-dom": "^5.7.0", - "@types/testing-library__react": "^9.1.2", - "@types/testing-library__react-hooks": "^3.1.0", + "@types/testing-library__jest-dom": "^5.9.2", + "@types/testing-library__react-hooks": "^3.4.0", "@types/type-detect": "^4.0.1", "@types/uuid": "^3.4.4", "@types/vinyl": "^2.0.4", diff --git a/packages/kbn-ace/README.md b/packages/kbn-ace/README.md new file mode 100644 index 0000000000000..54c422a72c6f8 --- /dev/null +++ b/packages/kbn-ace/README.md @@ -0,0 +1,5 @@ +# @kbn/ace + +Contains all Kibana-specific brace related code. Excluding the code that still inside of Console because that code is only used inside of console at the moment. + +This package enables plugins to use this functionality and import it as needed -- behind an async import so that brace does not bloat the JS code needed for first page load of Kibana. diff --git a/packages/kbn-ace/package.json b/packages/kbn-ace/package.json new file mode 100644 index 0000000000000..cf74d745f4cae --- /dev/null +++ b/packages/kbn-ace/package.json @@ -0,0 +1,20 @@ +{ + "name": "@kbn/ace", + "version": "1.0.0", + "private": true, + "main": "./target/index.js", + "license": "Apache-2.0", + "scripts": { + "build": "node ./scripts/build.js", + "kbn:bootstrap": "yarn build --dev" + }, + "dependencies": { + "brace": "0.11.1" + }, + "devDependencies": { + "@kbn/dev-utils": "1.0.0", + "@kbn/babel-preset": "1.0.0", + "raw-loader": "3.1.0", + "typescript": "4.0.2" + } +} diff --git a/packages/kbn-ace/scripts/build.js b/packages/kbn-ace/scripts/build.js new file mode 100644 index 0000000000000..2f570ffba1fc6 --- /dev/null +++ b/packages/kbn-ace/scripts/build.js @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const path = require('path'); +const del = require('del'); +const fs = require('fs'); +const supportsColor = require('supports-color'); +const { run } = require('@kbn/dev-utils'); + +const TARGET_BUILD_DIR = path.resolve(__dirname, '../target'); +const ROOT_DIR = path.resolve(__dirname, '../'); +const WORKER_PATH_SECTION = 'ace/modes/x_json/worker/x_json.ace.worker.js'; + +run( + async ({ procRunner, log }) => { + log.info('Deleting old output'); + + await del(TARGET_BUILD_DIR); + + const cwd = ROOT_DIR; + const env = { ...process.env }; + + if (supportsColor.stdout) { + env.FORCE_COLOR = 'true'; + } + + await procRunner.run('tsc ', { + cmd: 'tsc', + args: [], + wait: true, + env, + cwd, + }); + + log.success('Copying worker file to target.'); + + fs.copyFileSync( + path.resolve(__dirname, '..', 'src', WORKER_PATH_SECTION), + path.resolve(__dirname, '..', 'target', WORKER_PATH_SECTION) + ); + + log.success('Complete'); + }, + { + flags: { + boolean: ['dev'], + }, + } +); diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/index.ts b/packages/kbn-ace/src/ace/modes/index.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/ace/modes/index.ts rename to packages/kbn-ace/src/ace/modes/index.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts b/packages/kbn-ace/src/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts rename to packages/kbn-ace/src/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/index.ts b/packages/kbn-ace/src/ace/modes/lexer_rules/index.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/index.ts rename to packages/kbn-ace/src/ace/modes/lexer_rules/index.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/script_highlight_rules.ts b/packages/kbn-ace/src/ace/modes/lexer_rules/script_highlight_rules.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/script_highlight_rules.ts rename to packages/kbn-ace/src/ace/modes/lexer_rules/script_highlight_rules.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.ts b/packages/kbn-ace/src/ace/modes/lexer_rules/x_json_highlight_rules.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.ts rename to packages/kbn-ace/src/ace/modes/lexer_rules/x_json_highlight_rules.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/index.ts b/packages/kbn-ace/src/ace/modes/x_json/index.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/index.ts rename to packages/kbn-ace/src/ace/modes/x_json/index.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/index.ts b/packages/kbn-ace/src/ace/modes/x_json/worker/index.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/index.ts rename to packages/kbn-ace/src/ace/modes/x_json/worker/index.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/worker.d.ts b/packages/kbn-ace/src/ace/modes/x_json/worker/worker.d.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/worker.d.ts rename to packages/kbn-ace/src/ace/modes/x_json/worker/worker.d.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/x_json.ace.worker.js b/packages/kbn-ace/src/ace/modes/x_json/worker/x_json.ace.worker.js similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/x_json.ace.worker.js rename to packages/kbn-ace/src/ace/modes/x_json/worker/x_json.ace.worker.js diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/x_json.ts b/packages/kbn-ace/src/ace/modes/x_json/x_json.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/x_json.ts rename to packages/kbn-ace/src/ace/modes/x_json/x_json.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/index.ts b/packages/kbn-ace/src/index.ts similarity index 82% rename from src/plugins/es_ui_shared/public/console_lang/lib/index.ts rename to packages/kbn-ace/src/index.ts index bf7f0290d4158..62a6dbb948997 100644 --- a/src/plugins/es_ui_shared/public/console_lang/lib/index.ts +++ b/packages/kbn-ace/src/index.ts @@ -17,4 +17,11 @@ * under the License. */ -export { collapseLiteralStrings, expandLiteralStrings } from './json_xjson_translation_tools'; +export { + ElasticsearchSqlHighlightRules, + ScriptHighlightRules, + XJsonHighlightRules, + addXJsonToRules, + XJsonMode, + installXJsonMode, +} from './ace/modes'; diff --git a/packages/kbn-ace/tsconfig.json b/packages/kbn-ace/tsconfig.json new file mode 100644 index 0000000000000..6d3f433c6a6d1 --- /dev/null +++ b/packages/kbn-ace/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "declaration": true, + "sourceMap": true, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/kbn-ace/yarn.lock b/packages/kbn-ace/yarn.lock new file mode 120000 index 0000000000000..3f82ebc9cdbae --- /dev/null +++ b/packages/kbn-ace/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 9a3bb1c687032..902cc8839ac09 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(503); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(504); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; }); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(145); @@ -150,7 +150,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(127); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(496); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(497); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(143); /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -8763,9 +8763,9 @@ exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(128); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(287); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(395); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(396); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(288); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(396); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(397); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -8808,6 +8808,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(145); /* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(281); /* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(286); +/* harmony import */ var _utils_yarn_lock__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(283); +/* harmony import */ var _utils_validate_yarn_lock__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(287); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -8832,6 +8834,8 @@ __webpack_require__.r(__webpack_exports__); + + const BootstrapCommand = { description: 'Install dependencies and crosslink projects', name: 'bootstrap', @@ -8861,6 +8865,8 @@ const BootstrapCommand = { } } + const yarnLock = await Object(_utils_yarn_lock__WEBPACK_IMPORTED_MODULE_6__["readYarnLock"])(kbn); + await Object(_utils_validate_yarn_lock__WEBPACK_IMPORTED_MODULE_7__["validateYarnLock"])(kbn, yarnLock); await Object(_utils_link_project_executables__WEBPACK_IMPORTED_MODULE_0__["linkProjectExecutables"])(projects, projectGraph); /** * At the end of the bootstrapping process we call all `kbn:bootstrap` scripts @@ -8869,7 +8875,7 @@ const BootstrapCommand = { * have to, as it will slow down the bootstrapping process. */ - const checksums = await Object(_utils_project_checksums__WEBPACK_IMPORTED_MODULE_4__["getAllChecksums"])(kbn, _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"]); + const checksums = await Object(_utils_project_checksums__WEBPACK_IMPORTED_MODULE_4__["getAllChecksums"])(kbn, _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"], yarnLock); const caches = new Map(); let cachedProjectCount = 0; @@ -8987,6 +8993,7 @@ async function linkProjectExecutables(projectsByName, projectGraph) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "readFile", function() { return readFile; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "writeFile", function() { return writeFile; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "chmod", function() { return chmod; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mkdirp", function() { return mkdirp; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "unlink", function() { return unlink; }); @@ -9030,6 +9037,7 @@ __webpack_require__.r(__webpack_exports__); const lstat = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.lstat); const readFile = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.readFile); +const writeFile = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.writeFile); const symlink = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.symlink); const chmod = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.chmod); const cmdShim = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(cmd_shim__WEBPACK_IMPORTED_MODULE_0___default.a); @@ -29001,46 +29009,6 @@ async function getLatestSha(project, kbn) { }); return stdout.trim() || undefined; } -/** - * Get a list of the absolute dependencies of this project, as resolved - * in the yarn.lock file, does not include other projects in the workspace - * or their dependencies - */ - - -function resolveDepsForProject(project, yarnLock, kbn, log) { - /** map of [name@range, name@resolved] */ - const resolved = new Map(); - const queue = Object.entries(project.allDependencies); - - while (queue.length) { - const [name, versionRange] = queue.shift(); - const req = `${name}@${versionRange}`; - - if (resolved.has(req)) { - continue; - } - - if (!kbn.hasProject(name)) { - const pkg = yarnLock[req]; - - if (!pkg) { - log.warning('yarn.lock file is out of date, please run `yarn kbn bootstrap` to re-enable caching'); - return; - } - - const res = `${name}@${pkg.version}`; - resolved.set(req, res); - const allDepsEntries = [...Object.entries(pkg.dependencies || {}), ...Object.entries(pkg.optionalDependencies || {})]; - - for (const [childName, childVersionRange] of allDepsEntries) { - queue.push([childName, childVersionRange]); - } - } - } - - return Array.from(resolved.values()).sort((a, b) => a.localeCompare(b)); -} /** * Get the checksum for a specific project in the workspace */ @@ -29067,12 +29035,23 @@ async function getChecksum(project, changes, yarnLock, kbn, log) { log.verbose(`[${project.name}] modified time ${stats.mtimeMs} for ${path}`); return `${path}:${stats.mtimeMs}`; })); - const deps = await resolveDepsForProject(project, yarnLock, kbn, log); + const depMap = Object(_yarn_lock__WEBPACK_IMPORTED_MODULE_4__["resolveDepsForProject"])({ + project, + yarnLock, + kbn, + log, + includeDependentProject: false, + productionDepsOnly: false + }); - if (!deps) { + if (!depMap) { return; } + const deps = Array.from(depMap.values()).map(({ + name, + version + }) => `${name}@${version}`).sort((a, b) => a.localeCompare(b)); log.verbose(`[${project.name}] resolved %d deps`, deps.length); const checksum = JSON.stringify({ sha, @@ -29096,10 +29075,9 @@ async function getChecksum(project, changes, yarnLock, kbn, log) { */ -async function getAllChecksums(kbn, log) { +async function getAllChecksums(kbn, log, yarnLock) { const projects = kbn.getAllProjects(); const changesByProject = await getChangesForProjects(projects, kbn, log); - const yarnLock = await Object(_yarn_lock__WEBPACK_IMPORTED_MODULE_4__["readYarnLock"])(kbn); /** map of [project.name, cacheKey] */ const cacheKeys = new Map(); @@ -29122,6 +29100,7 @@ module.exports = require("crypto"); "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "readYarnLock", function() { return readYarnLock; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resolveDepsForProject", function() { return resolveDepsForProject; }); /* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(284); /* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(130); @@ -29143,7 +29122,7 @@ __webpack_require__.r(__webpack_exports__); * specific language governing permissions and limitations * under the License. */ -// @ts-ignore published types are worthless +// @ts-expect-error published types are worthless async function readYarnLock(kbn) { @@ -29164,6 +29143,75 @@ async function readYarnLock(kbn) { return {}; } +/** + * Get a list of the absolute dependencies of this project, as resolved + * in the yarn.lock file, does not include other projects in the workspace + * or their dependencies + */ + +function resolveDepsForProject({ + project: rootProject, + yarnLock, + kbn, + log, + productionDepsOnly, + includeDependentProject +}) { + /** map of [name@range, { name, version }] */ + const resolved = new Map(); + const seenProjects = new Set(); + const projectQueue = [rootProject]; + const depQueue = []; + + while (projectQueue.length) { + const project = projectQueue.shift(); + + if (seenProjects.has(project)) { + continue; + } + + seenProjects.add(project); + const projectDeps = Object.entries(productionDepsOnly ? project.productionDependencies : project.allDependencies); + + for (const [name, versionRange] of projectDeps) { + depQueue.push([name, versionRange]); + } + + while (depQueue.length) { + const [name, versionRange] = depQueue.shift(); + const req = `${name}@${versionRange}`; + + if (resolved.has(req)) { + continue; + } + + if (includeDependentProject && kbn.hasProject(name)) { + projectQueue.push(kbn.getProject(name)); + } + + if (!kbn.hasProject(name)) { + const pkg = yarnLock[req]; + + if (!pkg) { + log.warning('yarn.lock file is out of date, please run `yarn kbn bootstrap` to re-enable caching'); + return; + } + + resolved.set(req, { + name, + version: pkg.version + }); + const allDepsEntries = [...Object.entries(pkg.dependencies || {}), ...Object.entries(pkg.optionalDependencies || {})]; + + for (const [childName, childVersionRange] of allDepsEntries) { + depQueue.push([childName, childVersionRange]); + } + } + } + } + + return resolved; +} /***/ }), /* 284 */ @@ -39550,12 +39598,119 @@ class BootstrapCacheFile { /* 287 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "validateYarnLock", function() { return validateYarnLock; }); +/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(284); +/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(130); +/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(143); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +// @ts-expect-error published types are useless + + + + +async function validateYarnLock(kbn, yarnLock) { + // look through all of the packages in the yarn.lock file to see if + // we have accidentally installed multiple lodash v4 versions + const lodash4Versions = new Set(); + const lodash4Reqs = new Set(); + + for (const [req, dep] of Object.entries(yarnLock)) { + if (req.startsWith('lodash@') && dep.version.startsWith('4.')) { + lodash4Reqs.add(req); + lodash4Versions.add(dep.version); + } + } // if we find more than one lodash v4 version installed then delete + // lodash v4 requests from the yarn.lock file and prompt the user to + // retry bootstrap so that a single v4 version will be installed + + + if (lodash4Versions.size > 1) { + for (const req of lodash4Reqs) { + delete yarnLock[req]; + } + + await Object(_fs__WEBPACK_IMPORTED_MODULE_2__["writeFile"])(kbn.getAbsolute('yarn.lock'), Object(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__["stringify"])(yarnLock), 'utf8'); + _log__WEBPACK_IMPORTED_MODULE_3__["log"].error(dedent__WEBPACK_IMPORTED_MODULE_1___default.a` + + Multiple version of lodash v4 were detected, so they have been removed + from the yarn.lock file. Please rerun yarn kbn bootstrap to coalese the + lodash versions installed. + + If you still see this error when you re-bootstrap then you might need + to force a new dependency to use the latest version of lodash via the + "resolutions" field in package.json. + + If you have questions about this please reach out to the operations team. + + `); + process.exit(1); + } // look through all the dependencies of production packages and production + // dependencies of those packages to determine if we're shipping any versions + // of lodash v3 in the distributable + + + const prodDependencies = kbn.resolveAllProductionDependencies(yarnLock, _log__WEBPACK_IMPORTED_MODULE_3__["log"]); + const lodash3Versions = new Set(); + + for (const dep of prodDependencies.values()) { + if (dep.name === 'lodash' && dep.version.startsWith('3.')) { + lodash3Versions.add(dep.version); + } + } // if any lodash v3 packages were found we abort and tell the user to fix things + + + if (lodash3Versions.size) { + _log__WEBPACK_IMPORTED_MODULE_3__["log"].error(dedent__WEBPACK_IMPORTED_MODULE_1___default.a` + + Due to changes in the yarn.lock file and/or package.json files a version of + lodash 3 is now included in the production dependencies. To reduce the size of + our distributable and especially our front-end bundles we have decided to + prevent adding any new instances of lodash 3. + + Please inspect the changes to yarn.lock or package.json files to identify where + the lodash 3 version is coming from and remove it. + + If you have questions about this please reack out to the operations team. + + `); + process.exit(1); + } + + _log__WEBPACK_IMPORTED_MODULE_3__["log"].success('yarn.lock analysis completed without any issues'); +} + +/***/ }), +/* 288 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(288); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(289); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(375); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(376); /* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); @@ -39655,21 +39810,21 @@ const CleanCommand = { }; /***/ }), -/* 288 */ +/* 289 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(111); const path = __webpack_require__(4); -const globby = __webpack_require__(289); -const isGlob = __webpack_require__(367); -const slash = __webpack_require__(365); +const globby = __webpack_require__(290); +const isGlob = __webpack_require__(368); +const slash = __webpack_require__(366); const gracefulFs = __webpack_require__(132); -const isPathCwd = __webpack_require__(368); -const isPathInside = __webpack_require__(369); -const rimraf = __webpack_require__(370); -const pMap = __webpack_require__(371); +const isPathCwd = __webpack_require__(369); +const isPathInside = __webpack_require__(370); +const rimraf = __webpack_require__(371); +const pMap = __webpack_require__(372); const rimrafP = promisify(rimraf); @@ -39783,19 +39938,19 @@ module.exports.sync = (patterns, {force, dryRun, cwd = process.cwd(), ...options /***/ }), -/* 289 */ +/* 290 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(133); -const arrayUnion = __webpack_require__(290); -const merge2 = __webpack_require__(291); +const arrayUnion = __webpack_require__(291); +const merge2 = __webpack_require__(292); const glob = __webpack_require__(146); -const fastGlob = __webpack_require__(292); -const dirGlob = __webpack_require__(361); -const gitignore = __webpack_require__(363); -const {FilterStream, UniqueStream} = __webpack_require__(366); +const fastGlob = __webpack_require__(293); +const dirGlob = __webpack_require__(362); +const gitignore = __webpack_require__(364); +const {FilterStream, UniqueStream} = __webpack_require__(367); const DEFAULT_FILTER = () => false; @@ -39968,7 +40123,7 @@ module.exports.gitignore = gitignore; /***/ }), -/* 290 */ +/* 291 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -39980,7 +40135,7 @@ module.exports = (...arguments_) => { /***/ }), -/* 291 */ +/* 292 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40131,17 +40286,17 @@ function pauseStreams (streams, options) { /***/ }), -/* 292 */ +/* 293 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const taskManager = __webpack_require__(293); -const async_1 = __webpack_require__(322); -const stream_1 = __webpack_require__(357); -const sync_1 = __webpack_require__(358); -const settings_1 = __webpack_require__(360); -const utils = __webpack_require__(294); +const taskManager = __webpack_require__(294); +const async_1 = __webpack_require__(323); +const stream_1 = __webpack_require__(358); +const sync_1 = __webpack_require__(359); +const settings_1 = __webpack_require__(361); +const utils = __webpack_require__(295); async function FastGlob(source, options) { assertPatternsInput(source); const works = getWorks(source, async_1.default, options); @@ -40205,13 +40360,13 @@ module.exports = FastGlob; /***/ }), -/* 293 */ +/* 294 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(294); +const utils = __webpack_require__(295); function generate(patterns, settings) { const positivePatterns = getPositivePatterns(patterns); const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); @@ -40276,30 +40431,30 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 294 */ +/* 295 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const array = __webpack_require__(295); +const array = __webpack_require__(296); exports.array = array; -const errno = __webpack_require__(296); +const errno = __webpack_require__(297); exports.errno = errno; -const fs = __webpack_require__(297); +const fs = __webpack_require__(298); exports.fs = fs; -const path = __webpack_require__(298); +const path = __webpack_require__(299); exports.path = path; -const pattern = __webpack_require__(299); +const pattern = __webpack_require__(300); exports.pattern = pattern; -const stream = __webpack_require__(320); +const stream = __webpack_require__(321); exports.stream = stream; -const string = __webpack_require__(321); +const string = __webpack_require__(322); exports.string = string; /***/ }), -/* 295 */ +/* 296 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40327,7 +40482,7 @@ exports.splitWhen = splitWhen; /***/ }), -/* 296 */ +/* 297 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40340,7 +40495,7 @@ exports.isEnoentCodeError = isEnoentCodeError; /***/ }), -/* 297 */ +/* 298 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40365,7 +40520,7 @@ exports.createDirentFromStats = createDirentFromStats; /***/ }), -/* 298 */ +/* 299 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40404,16 +40559,16 @@ exports.removeLeadingDotSegment = removeLeadingDotSegment; /***/ }), -/* 299 */ +/* 300 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const globParent = __webpack_require__(300); -const micromatch = __webpack_require__(303); -const picomatch = __webpack_require__(314); +const globParent = __webpack_require__(301); +const micromatch = __webpack_require__(304); +const picomatch = __webpack_require__(315); const GLOBSTAR = '**'; const ESCAPE_SYMBOL = '\\'; const COMMON_GLOB_SYMBOLS_RE = /[*?]|^!/; @@ -40523,13 +40678,13 @@ exports.matchAny = matchAny; /***/ }), -/* 300 */ +/* 301 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isGlob = __webpack_require__(301); +var isGlob = __webpack_require__(302); var pathPosixDirname = __webpack_require__(4).posix.dirname; var isWin32 = __webpack_require__(120).platform() === 'win32'; @@ -40571,7 +40726,7 @@ module.exports = function globParent(str, opts) { /***/ }), -/* 301 */ +/* 302 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -40581,7 +40736,7 @@ module.exports = function globParent(str, opts) { * Released under the MIT License. */ -var isExtglob = __webpack_require__(302); +var isExtglob = __webpack_require__(303); var chars = { '{': '}', '(': ')', '[': ']'}; var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; @@ -40625,7 +40780,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 302 */ +/* 303 */ /***/ (function(module, exports) { /*! @@ -40651,16 +40806,16 @@ module.exports = function isExtglob(str) { /***/ }), -/* 303 */ +/* 304 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const util = __webpack_require__(111); -const braces = __webpack_require__(304); -const picomatch = __webpack_require__(314); -const utils = __webpack_require__(317); +const braces = __webpack_require__(305); +const picomatch = __webpack_require__(315); +const utils = __webpack_require__(318); const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); /** @@ -41125,16 +41280,16 @@ module.exports = micromatch; /***/ }), -/* 304 */ +/* 305 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(305); -const compile = __webpack_require__(307); -const expand = __webpack_require__(311); -const parse = __webpack_require__(312); +const stringify = __webpack_require__(306); +const compile = __webpack_require__(308); +const expand = __webpack_require__(312); +const parse = __webpack_require__(313); /** * Expand the given pattern or create a regex-compatible string. @@ -41302,13 +41457,13 @@ module.exports = braces; /***/ }), -/* 305 */ +/* 306 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const utils = __webpack_require__(306); +const utils = __webpack_require__(307); module.exports = (ast, options = {}) => { let stringify = (node, parent = {}) => { @@ -41341,7 +41496,7 @@ module.exports = (ast, options = {}) => { /***/ }), -/* 306 */ +/* 307 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41460,14 +41615,14 @@ exports.flatten = (...args) => { /***/ }), -/* 307 */ +/* 308 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(308); -const utils = __webpack_require__(306); +const fill = __webpack_require__(309); +const utils = __webpack_require__(307); const compile = (ast, options = {}) => { let walk = (node, parent = {}) => { @@ -41524,7 +41679,7 @@ module.exports = compile; /***/ }), -/* 308 */ +/* 309 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41538,7 +41693,7 @@ module.exports = compile; const util = __webpack_require__(111); -const toRegexRange = __webpack_require__(309); +const toRegexRange = __webpack_require__(310); const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); @@ -41780,7 +41935,7 @@ module.exports = fill; /***/ }), -/* 309 */ +/* 310 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41793,7 +41948,7 @@ module.exports = fill; -const isNumber = __webpack_require__(310); +const isNumber = __webpack_require__(311); const toRegexRange = (min, max, options) => { if (isNumber(min) === false) { @@ -42075,7 +42230,7 @@ module.exports = toRegexRange; /***/ }), -/* 310 */ +/* 311 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42100,15 +42255,15 @@ module.exports = function(num) { /***/ }), -/* 311 */ +/* 312 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(308); -const stringify = __webpack_require__(305); -const utils = __webpack_require__(306); +const fill = __webpack_require__(309); +const stringify = __webpack_require__(306); +const utils = __webpack_require__(307); const append = (queue = '', stash = '', enclose = false) => { let result = []; @@ -42220,13 +42375,13 @@ module.exports = expand; /***/ }), -/* 312 */ +/* 313 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(305); +const stringify = __webpack_require__(306); /** * Constants @@ -42248,7 +42403,7 @@ const { CHAR_SINGLE_QUOTE, /* ' */ CHAR_NO_BREAK_SPACE, CHAR_ZERO_WIDTH_NOBREAK_SPACE -} = __webpack_require__(313); +} = __webpack_require__(314); /** * parse @@ -42560,7 +42715,7 @@ module.exports = parse; /***/ }), -/* 313 */ +/* 314 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42624,27 +42779,27 @@ module.exports = { /***/ }), -/* 314 */ +/* 315 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = __webpack_require__(315); +module.exports = __webpack_require__(316); /***/ }), -/* 315 */ +/* 316 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const scan = __webpack_require__(316); -const parse = __webpack_require__(319); -const utils = __webpack_require__(317); -const constants = __webpack_require__(318); +const scan = __webpack_require__(317); +const parse = __webpack_require__(320); +const utils = __webpack_require__(318); +const constants = __webpack_require__(319); const isObject = val => val && typeof val === 'object' && !Array.isArray(val); /** @@ -42980,13 +43135,13 @@ module.exports = picomatch; /***/ }), -/* 316 */ +/* 317 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const utils = __webpack_require__(317); +const utils = __webpack_require__(318); const { CHAR_ASTERISK, /* * */ CHAR_AT, /* @ */ @@ -43003,7 +43158,7 @@ const { CHAR_RIGHT_CURLY_BRACE, /* } */ CHAR_RIGHT_PARENTHESES, /* ) */ CHAR_RIGHT_SQUARE_BRACKET /* ] */ -} = __webpack_require__(318); +} = __webpack_require__(319); const isPathSeparator = code => { return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; @@ -43370,7 +43525,7 @@ module.exports = scan; /***/ }), -/* 317 */ +/* 318 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43383,7 +43538,7 @@ const { REGEX_REMOVE_BACKSLASH, REGEX_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_GLOBAL -} = __webpack_require__(318); +} = __webpack_require__(319); exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); @@ -43441,7 +43596,7 @@ exports.wrapOutput = (input, state = {}, options = {}) => { /***/ }), -/* 318 */ +/* 319 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43627,14 +43782,14 @@ module.exports = { /***/ }), -/* 319 */ +/* 320 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const constants = __webpack_require__(318); -const utils = __webpack_require__(317); +const constants = __webpack_require__(319); +const utils = __webpack_require__(318); /** * Constants @@ -44712,13 +44867,13 @@ module.exports = parse; /***/ }), -/* 320 */ +/* 321 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const merge2 = __webpack_require__(291); +const merge2 = __webpack_require__(292); function merge(streams) { const mergedStream = merge2(streams); streams.forEach((stream) => { @@ -44735,7 +44890,7 @@ function propagateCloseEventToSources(streams) { /***/ }), -/* 321 */ +/* 322 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44752,14 +44907,14 @@ exports.isEmpty = isEmpty; /***/ }), -/* 322 */ +/* 323 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(323); -const provider_1 = __webpack_require__(350); +const stream_1 = __webpack_require__(324); +const provider_1 = __webpack_require__(351); class ProviderAsync extends provider_1.default { constructor() { super(...arguments); @@ -44787,16 +44942,16 @@ exports.default = ProviderAsync; /***/ }), -/* 323 */ +/* 324 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(137); -const fsStat = __webpack_require__(324); -const fsWalk = __webpack_require__(329); -const reader_1 = __webpack_require__(349); +const fsStat = __webpack_require__(325); +const fsWalk = __webpack_require__(330); +const reader_1 = __webpack_require__(350); class ReaderStream extends reader_1.default { constructor() { super(...arguments); @@ -44849,15 +45004,15 @@ exports.default = ReaderStream; /***/ }), -/* 324 */ +/* 325 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async = __webpack_require__(325); -const sync = __webpack_require__(326); -const settings_1 = __webpack_require__(327); +const async = __webpack_require__(326); +const sync = __webpack_require__(327); +const settings_1 = __webpack_require__(328); exports.Settings = settings_1.default; function stat(path, optionsOrSettingsOrCallback, callback) { if (typeof optionsOrSettingsOrCallback === 'function') { @@ -44880,7 +45035,7 @@ function getSettings(settingsOrOptions = {}) { /***/ }), -/* 325 */ +/* 326 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44918,7 +45073,7 @@ function callSuccessCallback(callback, result) { /***/ }), -/* 326 */ +/* 327 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44947,13 +45102,13 @@ exports.read = read; /***/ }), -/* 327 */ +/* 328 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(328); +const fs = __webpack_require__(329); class Settings { constructor(_options = {}) { this._options = _options; @@ -44970,7 +45125,7 @@ exports.default = Settings; /***/ }), -/* 328 */ +/* 329 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44993,16 +45148,16 @@ exports.createFileSystemAdapter = createFileSystemAdapter; /***/ }), -/* 329 */ +/* 330 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async_1 = __webpack_require__(330); -const stream_1 = __webpack_require__(345); -const sync_1 = __webpack_require__(346); -const settings_1 = __webpack_require__(348); +const async_1 = __webpack_require__(331); +const stream_1 = __webpack_require__(346); +const sync_1 = __webpack_require__(347); +const settings_1 = __webpack_require__(349); exports.Settings = settings_1.default; function walk(directory, optionsOrSettingsOrCallback, callback) { if (typeof optionsOrSettingsOrCallback === 'function') { @@ -45032,13 +45187,13 @@ function getSettings(settingsOrOptions = {}) { /***/ }), -/* 330 */ +/* 331 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async_1 = __webpack_require__(331); +const async_1 = __webpack_require__(332); class AsyncProvider { constructor(_root, _settings) { this._root = _root; @@ -45069,17 +45224,17 @@ function callSuccessCallback(callback, entries) { /***/ }), -/* 331 */ +/* 332 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = __webpack_require__(155); -const fsScandir = __webpack_require__(332); -const fastq = __webpack_require__(341); -const common = __webpack_require__(343); -const reader_1 = __webpack_require__(344); +const fsScandir = __webpack_require__(333); +const fastq = __webpack_require__(342); +const common = __webpack_require__(344); +const reader_1 = __webpack_require__(345); class AsyncReader extends reader_1.default { constructor(_root, _settings) { super(_root, _settings); @@ -45169,15 +45324,15 @@ exports.default = AsyncReader; /***/ }), -/* 332 */ +/* 333 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async = __webpack_require__(333); -const sync = __webpack_require__(338); -const settings_1 = __webpack_require__(339); +const async = __webpack_require__(334); +const sync = __webpack_require__(339); +const settings_1 = __webpack_require__(340); exports.Settings = settings_1.default; function scandir(path, optionsOrSettingsOrCallback, callback) { if (typeof optionsOrSettingsOrCallback === 'function') { @@ -45200,16 +45355,16 @@ function getSettings(settingsOrOptions = {}) { /***/ }), -/* 333 */ +/* 334 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(324); -const rpl = __webpack_require__(334); -const constants_1 = __webpack_require__(335); -const utils = __webpack_require__(336); +const fsStat = __webpack_require__(325); +const rpl = __webpack_require__(335); +const constants_1 = __webpack_require__(336); +const utils = __webpack_require__(337); function read(directory, settings, callback) { if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { return readdirWithFileTypes(directory, settings, callback); @@ -45297,7 +45452,7 @@ function callSuccessCallback(callback, result) { /***/ }), -/* 334 */ +/* 335 */ /***/ (function(module, exports) { module.exports = runParallel @@ -45351,7 +45506,7 @@ function runParallel (tasks, cb) { /***/ }), -/* 335 */ +/* 336 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45371,18 +45526,18 @@ exports.IS_SUPPORT_READDIR_WITH_FILE_TYPES = IS_MATCHED_BY_MAJOR || IS_MATCHED_B /***/ }), -/* 336 */ +/* 337 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(337); +const fs = __webpack_require__(338); exports.fs = fs; /***/ }), -/* 337 */ +/* 338 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45407,15 +45562,15 @@ exports.createDirentFromStats = createDirentFromStats; /***/ }), -/* 338 */ +/* 339 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(324); -const constants_1 = __webpack_require__(335); -const utils = __webpack_require__(336); +const fsStat = __webpack_require__(325); +const constants_1 = __webpack_require__(336); +const utils = __webpack_require__(337); function read(directory, settings) { if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { return readdirWithFileTypes(directory, settings); @@ -45466,15 +45621,15 @@ exports.readdir = readdir; /***/ }), -/* 339 */ +/* 340 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const fsStat = __webpack_require__(324); -const fs = __webpack_require__(340); +const fsStat = __webpack_require__(325); +const fs = __webpack_require__(341); class Settings { constructor(_options = {}) { this._options = _options; @@ -45497,7 +45652,7 @@ exports.default = Settings; /***/ }), -/* 340 */ +/* 341 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45522,13 +45677,13 @@ exports.createFileSystemAdapter = createFileSystemAdapter; /***/ }), -/* 341 */ +/* 342 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var reusify = __webpack_require__(342) +var reusify = __webpack_require__(343) function fastqueue (context, worker, concurrency) { if (typeof context === 'function') { @@ -45702,7 +45857,7 @@ module.exports = fastqueue /***/ }), -/* 342 */ +/* 343 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45742,7 +45897,7 @@ module.exports = reusify /***/ }), -/* 343 */ +/* 344 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45773,13 +45928,13 @@ exports.joinPathSegments = joinPathSegments; /***/ }), -/* 344 */ +/* 345 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const common = __webpack_require__(343); +const common = __webpack_require__(344); class Reader { constructor(_root, _settings) { this._root = _root; @@ -45791,14 +45946,14 @@ exports.default = Reader; /***/ }), -/* 345 */ +/* 346 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(137); -const async_1 = __webpack_require__(331); +const async_1 = __webpack_require__(332); class StreamProvider { constructor(_root, _settings) { this._root = _root; @@ -45828,13 +45983,13 @@ exports.default = StreamProvider; /***/ }), -/* 346 */ +/* 347 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(347); +const sync_1 = __webpack_require__(348); class SyncProvider { constructor(_root, _settings) { this._root = _root; @@ -45849,15 +46004,15 @@ exports.default = SyncProvider; /***/ }), -/* 347 */ +/* 348 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsScandir = __webpack_require__(332); -const common = __webpack_require__(343); -const reader_1 = __webpack_require__(344); +const fsScandir = __webpack_require__(333); +const common = __webpack_require__(344); +const reader_1 = __webpack_require__(345); class SyncReader extends reader_1.default { constructor() { super(...arguments); @@ -45915,14 +46070,14 @@ exports.default = SyncReader; /***/ }), -/* 348 */ +/* 349 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const fsScandir = __webpack_require__(332); +const fsScandir = __webpack_require__(333); class Settings { constructor(_options = {}) { this._options = _options; @@ -45948,15 +46103,15 @@ exports.default = Settings; /***/ }), -/* 349 */ +/* 350 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const fsStat = __webpack_require__(324); -const utils = __webpack_require__(294); +const fsStat = __webpack_require__(325); +const utils = __webpack_require__(295); class Reader { constructor(_settings) { this._settings = _settings; @@ -45988,17 +46143,17 @@ exports.default = Reader; /***/ }), -/* 350 */ +/* 351 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const deep_1 = __webpack_require__(351); -const entry_1 = __webpack_require__(354); -const error_1 = __webpack_require__(355); -const entry_2 = __webpack_require__(356); +const deep_1 = __webpack_require__(352); +const entry_1 = __webpack_require__(355); +const error_1 = __webpack_require__(356); +const entry_2 = __webpack_require__(357); class Provider { constructor(_settings) { this._settings = _settings; @@ -46043,14 +46198,14 @@ exports.default = Provider; /***/ }), -/* 351 */ +/* 352 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(294); -const partial_1 = __webpack_require__(352); +const utils = __webpack_require__(295); +const partial_1 = __webpack_require__(353); class DeepFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -46104,13 +46259,13 @@ exports.default = DeepFilter; /***/ }), -/* 352 */ +/* 353 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const matcher_1 = __webpack_require__(353); +const matcher_1 = __webpack_require__(354); class PartialMatcher extends matcher_1.default { match(filepath) { const parts = filepath.split('/'); @@ -46149,13 +46304,13 @@ exports.default = PartialMatcher; /***/ }), -/* 353 */ +/* 354 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(294); +const utils = __webpack_require__(295); class Matcher { constructor(_patterns, _settings, _micromatchOptions) { this._patterns = _patterns; @@ -46206,13 +46361,13 @@ exports.default = Matcher; /***/ }), -/* 354 */ +/* 355 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(294); +const utils = __webpack_require__(295); class EntryFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -46268,13 +46423,13 @@ exports.default = EntryFilter; /***/ }), -/* 355 */ +/* 356 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(294); +const utils = __webpack_require__(295); class ErrorFilter { constructor(_settings) { this._settings = _settings; @@ -46290,13 +46445,13 @@ exports.default = ErrorFilter; /***/ }), -/* 356 */ +/* 357 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(294); +const utils = __webpack_require__(295); class EntryTransformer { constructor(_settings) { this._settings = _settings; @@ -46323,15 +46478,15 @@ exports.default = EntryTransformer; /***/ }), -/* 357 */ +/* 358 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(137); -const stream_2 = __webpack_require__(323); -const provider_1 = __webpack_require__(350); +const stream_2 = __webpack_require__(324); +const provider_1 = __webpack_require__(351); class ProviderStream extends provider_1.default { constructor() { super(...arguments); @@ -46361,14 +46516,14 @@ exports.default = ProviderStream; /***/ }), -/* 358 */ +/* 359 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(359); -const provider_1 = __webpack_require__(350); +const sync_1 = __webpack_require__(360); +const provider_1 = __webpack_require__(351); class ProviderSync extends provider_1.default { constructor() { super(...arguments); @@ -46391,15 +46546,15 @@ exports.default = ProviderSync; /***/ }), -/* 359 */ +/* 360 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(324); -const fsWalk = __webpack_require__(329); -const reader_1 = __webpack_require__(349); +const fsStat = __webpack_require__(325); +const fsWalk = __webpack_require__(330); +const reader_1 = __webpack_require__(350); class ReaderSync extends reader_1.default { constructor() { super(...arguments); @@ -46441,7 +46596,7 @@ exports.default = ReaderSync; /***/ }), -/* 360 */ +/* 361 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46500,13 +46655,13 @@ exports.default = Settings; /***/ }), -/* 361 */ +/* 362 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(362); +const pathType = __webpack_require__(363); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -46582,7 +46737,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 362 */ +/* 363 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46632,7 +46787,7 @@ exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 363 */ +/* 364 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46640,9 +46795,9 @@ exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); const {promisify} = __webpack_require__(111); const fs = __webpack_require__(133); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(292); -const gitIgnore = __webpack_require__(364); -const slash = __webpack_require__(365); +const fastGlob = __webpack_require__(293); +const gitIgnore = __webpack_require__(365); +const slash = __webpack_require__(366); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -46756,7 +46911,7 @@ module.exports.sync = options => { /***/ }), -/* 364 */ +/* 365 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -47359,7 +47514,7 @@ if ( /***/ }), -/* 365 */ +/* 366 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47377,7 +47532,7 @@ module.exports = path => { /***/ }), -/* 366 */ +/* 367 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47430,7 +47585,7 @@ module.exports = { /***/ }), -/* 367 */ +/* 368 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -47440,7 +47595,7 @@ module.exports = { * Released under the MIT License. */ -var isExtglob = __webpack_require__(302); +var isExtglob = __webpack_require__(303); var chars = { '{': '}', '(': ')', '[': ']'}; var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; @@ -47484,7 +47639,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 368 */ +/* 369 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47506,7 +47661,7 @@ module.exports = path_ => { /***/ }), -/* 369 */ +/* 370 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47534,7 +47689,7 @@ module.exports = (childPath, parentPath) => { /***/ }), -/* 370 */ +/* 371 */ /***/ (function(module, exports, __webpack_require__) { const assert = __webpack_require__(139) @@ -47900,12 +48055,12 @@ rimraf.sync = rimrafSync /***/ }), -/* 371 */ +/* 372 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const AggregateError = __webpack_require__(372); +const AggregateError = __webpack_require__(373); module.exports = async ( iterable, @@ -47988,13 +48143,13 @@ module.exports = async ( /***/ }), -/* 372 */ +/* 373 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const indentString = __webpack_require__(373); -const cleanStack = __webpack_require__(374); +const indentString = __webpack_require__(374); +const cleanStack = __webpack_require__(375); const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); @@ -48042,7 +48197,7 @@ module.exports = AggregateError; /***/ }), -/* 373 */ +/* 374 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48084,7 +48239,7 @@ module.exports = (string, count = 1, options) => { /***/ }), -/* 374 */ +/* 375 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48131,15 +48286,15 @@ module.exports = (stack, options) => { /***/ }), -/* 375 */ +/* 376 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(376); -const cliCursor = __webpack_require__(385); -const cliSpinners = __webpack_require__(389); -const logSymbols = __webpack_require__(391); +const chalk = __webpack_require__(377); +const cliCursor = __webpack_require__(386); +const cliSpinners = __webpack_require__(390); +const logSymbols = __webpack_require__(392); class Ora { constructor(options) { @@ -48286,16 +48441,16 @@ module.exports.promise = (action, options) => { /***/ }), -/* 376 */ +/* 377 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(178); -const ansiStyles = __webpack_require__(377); -const stdoutColor = __webpack_require__(382).stdout; +const ansiStyles = __webpack_require__(378); +const stdoutColor = __webpack_require__(383).stdout; -const template = __webpack_require__(384); +const template = __webpack_require__(385); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -48521,12 +48676,12 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 377 */ +/* 378 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(378); +const colorConvert = __webpack_require__(379); const wrapAnsi16 = (fn, offset) => function () { const code = fn.apply(colorConvert, arguments); @@ -48694,11 +48849,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(114)(module))) /***/ }), -/* 378 */ +/* 379 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(379); -var route = __webpack_require__(381); +var conversions = __webpack_require__(380); +var route = __webpack_require__(382); var convert = {}; @@ -48778,11 +48933,11 @@ module.exports = convert; /***/ }), -/* 379 */ +/* 380 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ -var cssKeywords = __webpack_require__(380); +var cssKeywords = __webpack_require__(381); // NOTE: conversions should only return primitive values (i.e. arrays, or // values that give correct `typeof` results). @@ -49652,7 +49807,7 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 380 */ +/* 381 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49811,10 +49966,10 @@ module.exports = { /***/ }), -/* 381 */ +/* 382 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(379); +var conversions = __webpack_require__(380); /* this function routes a model to all other models. @@ -49914,13 +50069,13 @@ module.exports = function (fromModel) { /***/ }), -/* 382 */ +/* 383 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const os = __webpack_require__(120); -const hasFlag = __webpack_require__(383); +const hasFlag = __webpack_require__(384); const env = process.env; @@ -50052,7 +50207,7 @@ module.exports = { /***/ }), -/* 383 */ +/* 384 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50067,7 +50222,7 @@ module.exports = (flag, argv) => { /***/ }), -/* 384 */ +/* 385 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50202,12 +50357,12 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 385 */ +/* 386 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(386); +const restoreCursor = __webpack_require__(387); let hidden = false; @@ -50248,12 +50403,12 @@ exports.toggle = (force, stream) => { /***/ }), -/* 386 */ +/* 387 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const onetime = __webpack_require__(387); +const onetime = __webpack_require__(388); const signalExit = __webpack_require__(217); module.exports = onetime(() => { @@ -50264,12 +50419,12 @@ module.exports = onetime(() => { /***/ }), -/* 387 */ +/* 388 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const mimicFn = __webpack_require__(388); +const mimicFn = __webpack_require__(389); module.exports = (fn, opts) => { // TODO: Remove this in v3 @@ -50310,7 +50465,7 @@ module.exports = (fn, opts) => { /***/ }), -/* 388 */ +/* 389 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50326,27 +50481,27 @@ module.exports = (to, from) => { /***/ }), -/* 389 */ +/* 390 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = __webpack_require__(390); +module.exports = __webpack_require__(391); /***/ }), -/* 390 */ +/* 391 */ /***/ (function(module) { module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]}}"); /***/ }), -/* 391 */ +/* 392 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(392); +const chalk = __webpack_require__(393); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -50368,16 +50523,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 392 */ +/* 393 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(178); -const ansiStyles = __webpack_require__(393); +const ansiStyles = __webpack_require__(394); const stdoutColor = __webpack_require__(184).stdout; -const template = __webpack_require__(394); +const template = __webpack_require__(395); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -50603,7 +50758,7 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 393 */ +/* 394 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50776,7 +50931,7 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(114)(module))) /***/ }), -/* 394 */ +/* 395 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50911,7 +51066,7 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 395 */ +/* 396 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -50972,7 +51127,7 @@ const RunCommand = { }; /***/ }), -/* 396 */ +/* 397 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -50982,7 +51137,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(144); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(145); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(397); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(398); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -51067,14 +51222,14 @@ const WatchCommand = { }; /***/ }), -/* 397 */ +/* 398 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; }); /* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8); -/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(398); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(399); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -51141,141 +51296,141 @@ function waitUntilWatchIsReady(stream, opts = {}) { } /***/ }), -/* 398 */ +/* 399 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(399); +/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(400); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; }); -/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(400); +/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(401); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; }); -/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(401); +/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(402); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; }); -/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(402); +/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(403); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__["bufferCount"]; }); -/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(403); +/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(404); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; }); -/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(404); +/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(405); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; }); -/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(405); +/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(406); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; }); -/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(406); +/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(407); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; }); -/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(407); +/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(408); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; }); -/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(408); +/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(409); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; }); -/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(409); +/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(410); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; }); /* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(80); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; }); -/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(410); +/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(411); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; }); -/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(411); +/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(412); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; }); -/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(412); +/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(413); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; }); -/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(413); +/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(414); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; }); -/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(414); +/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(415); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; }); -/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(415); +/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(416); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; }); -/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(416); +/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(417); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; }); -/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(418); +/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(419); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; }); -/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(419); +/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(420); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; }); -/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(420); +/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(421); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; }); -/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(421); +/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(422); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; }); -/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(422); +/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(423); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; }); -/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(423); +/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(424); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; }); -/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(426); +/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(427); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; }); -/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(427); +/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(428); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; }); -/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(428); +/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(429); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; }); -/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(429); +/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(430); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; }); -/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(430); +/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(431); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); /* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(104); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); -/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(431); +/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(432); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; }); -/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(432); +/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(433); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; }); -/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(433); +/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(434); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; }); -/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(434); +/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(435); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; }); /* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(31); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__["groupBy"]; }); -/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(435); +/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(436); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; }); -/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(436); +/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(437); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; }); -/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(437); +/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(438); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; }); /* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(66); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; }); -/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(439); +/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(440); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; }); -/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(440); +/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(441); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; }); -/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(441); +/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(442); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; }); -/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(444); +/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(445); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; }); /* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(81); @@ -51286,175 +51441,175 @@ __webpack_require__.r(__webpack_exports__); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); -/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(445); +/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(446); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; }); -/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(446); +/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(447); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; }); -/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(447); +/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(448); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "min", function() { return _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__["min"]; }); -/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(448); +/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(449); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; }); /* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(41); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; }); -/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(449); +/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(450); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; }); -/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(450); +/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(451); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; }); -/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(451); +/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(452); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; }); -/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(452); +/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(453); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; }); -/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(453); +/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(454); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; }); -/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(454); +/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(455); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; }); -/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(455); +/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(456); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; }); -/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(456); +/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(457); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; }); -/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(457); +/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(458); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; }); -/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(442); +/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(443); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; }); -/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(458); +/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(459); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; }); -/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(459); +/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(460); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; }); -/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(460); +/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(461); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; }); -/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(461); +/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(462); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; }); /* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(30); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; }); -/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(462); +/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(463); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; }); -/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(463); +/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(464); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; }); -/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(443); +/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(444); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; }); -/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(464); +/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(465); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; }); -/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(465); +/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(466); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; }); -/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(466); +/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(467); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; }); -/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(467); +/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(468); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; }); -/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(468); +/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(469); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; }); -/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(469); +/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(470); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; }); -/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(470); +/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(471); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; }); -/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(471); +/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(472); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; }); -/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(472); +/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(473); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; }); -/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(473); +/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(474); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; }); -/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(475); +/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(476); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; }); -/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(476); +/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(477); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__["switchMap"]; }); -/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(477); +/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(478); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__["switchMapTo"]; }); -/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(425); +/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(426); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "take", function() { return _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__["take"]; }); -/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(438); +/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(439); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__["takeLast"]; }); -/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(478); +/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(479); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__["takeUntil"]; }); -/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(479); +/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(480); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__["takeWhile"]; }); -/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(480); +/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(481); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__["tap"]; }); -/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(481); +/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(482); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__["throttle"]; }); -/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(482); +/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(483); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__["throttleTime"]; }); -/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(424); +/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(425); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__["throwIfEmpty"]; }); -/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(483); +/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(484); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__["timeInterval"]; }); -/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(484); +/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(485); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__["timeout"]; }); -/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(485); +/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(486); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__["timeoutWith"]; }); -/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(486); +/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(487); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__["timestamp"]; }); -/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(487); +/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(488); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__["toArray"]; }); -/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(488); +/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(489); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "window", function() { return _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__["window"]; }); -/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(489); +/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(490); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__["windowCount"]; }); -/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(490); +/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(491); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__["windowTime"]; }); -/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(491); +/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(492); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__["windowToggle"]; }); -/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(492); +/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(493); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__["windowWhen"]; }); -/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(493); +/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(494); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__["withLatestFrom"]; }); -/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(494); +/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(495); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__["zip"]; }); -/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(495); +/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(496); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__["zipAll"]; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ @@ -51566,7 +51721,7 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 399 */ +/* 400 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51647,14 +51802,14 @@ var AuditSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 400 */ +/* 401 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return auditTime; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); -/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(399); +/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(400); /* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(107); /** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ @@ -51670,7 +51825,7 @@ function auditTime(duration, scheduler) { /***/ }), -/* 401 */ +/* 402 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51719,7 +51874,7 @@ var BufferSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 402 */ +/* 403 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51820,7 +51975,7 @@ var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 403 */ +/* 404 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51981,7 +52136,7 @@ function dispatchBufferClose(arg) { /***/ }), -/* 404 */ +/* 405 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52101,7 +52256,7 @@ var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 405 */ +/* 406 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52196,7 +52351,7 @@ var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 406 */ +/* 407 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52260,7 +52415,7 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 407 */ +/* 408 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52276,7 +52431,7 @@ function combineAll(project) { /***/ }), -/* 408 */ +/* 409 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52308,7 +52463,7 @@ function combineLatest() { /***/ }), -/* 409 */ +/* 410 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52328,7 +52483,7 @@ function concat() { /***/ }), -/* 410 */ +/* 411 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52344,13 +52499,13 @@ function concatMap(project, resultSelector) { /***/ }), -/* 411 */ +/* 412 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return concatMapTo; }); -/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(410); +/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(411); /** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ function concatMapTo(innerObservable, resultSelector) { @@ -52360,7 +52515,7 @@ function concatMapTo(innerObservable, resultSelector) { /***/ }), -/* 412 */ +/* 413 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52425,7 +52580,7 @@ var CountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 413 */ +/* 414 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52513,7 +52668,7 @@ var DebounceSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 414 */ +/* 415 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52589,7 +52744,7 @@ function dispatchNext(subscriber) { /***/ }), -/* 415 */ +/* 416 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52639,7 +52794,7 @@ var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 416 */ +/* 417 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52647,7 +52802,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return delay; }); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(417); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(418); /* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11); /* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(42); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */ @@ -52746,7 +52901,7 @@ var DelayMessage = /*@__PURE__*/ (function () { /***/ }), -/* 417 */ +/* 418 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52760,7 +52915,7 @@ function isDate(value) { /***/ }), -/* 418 */ +/* 419 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52906,7 +53061,7 @@ var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 419 */ +/* 420 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52944,7 +53099,7 @@ var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 420 */ +/* 421 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53022,7 +53177,7 @@ var DistinctSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 421 */ +/* 422 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53093,13 +53248,13 @@ var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 422 */ +/* 423 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return distinctUntilKeyChanged; }); -/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(421); +/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(422); /** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ function distinctUntilKeyChanged(key, compare) { @@ -53109,7 +53264,7 @@ function distinctUntilKeyChanged(key, compare) { /***/ }), -/* 423 */ +/* 424 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53117,9 +53272,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return elementAt; }); /* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(62); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(104); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(424); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(415); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(425); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(425); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(416); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(426); /** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ @@ -53141,7 +53296,7 @@ function elementAt(index, defaultValue) { /***/ }), -/* 424 */ +/* 425 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53207,7 +53362,7 @@ function defaultErrorFactory() { /***/ }), -/* 425 */ +/* 426 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53269,7 +53424,7 @@ var TakeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 426 */ +/* 427 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53291,7 +53446,7 @@ function endWith() { /***/ }), -/* 427 */ +/* 428 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53353,7 +53508,7 @@ var EverySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 428 */ +/* 429 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53410,7 +53565,7 @@ var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 429 */ +/* 430 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53510,7 +53665,7 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 430 */ +/* 431 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53629,7 +53784,7 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 431 */ +/* 432 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53667,7 +53822,7 @@ var FinallySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 432 */ +/* 433 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53739,13 +53894,13 @@ var FindValueSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 433 */ +/* 434 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return findIndex; }); -/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(432); +/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(433); /** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ function findIndex(predicate, thisArg) { @@ -53755,7 +53910,7 @@ function findIndex(predicate, thisArg) { /***/ }), -/* 434 */ +/* 435 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53763,9 +53918,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "first", function() { return first; }); /* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(104); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(425); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(415); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(424); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(426); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(416); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(425); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); /** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -53782,7 +53937,7 @@ function first(predicate, defaultValue) { /***/ }), -/* 435 */ +/* 436 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53819,7 +53974,7 @@ var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 436 */ +/* 437 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53863,7 +54018,7 @@ var IsEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 437 */ +/* 438 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53871,9 +54026,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "last", function() { return last; }); /* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(104); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(438); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(424); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(415); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(439); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(425); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(416); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); /** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -53890,7 +54045,7 @@ function last(predicate, defaultValue) { /***/ }), -/* 438 */ +/* 439 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53967,7 +54122,7 @@ var TakeLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 439 */ +/* 440 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54006,7 +54161,7 @@ var MapToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 440 */ +/* 441 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54056,13 +54211,13 @@ var MaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 441 */ +/* 442 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "max", function() { return max; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(442); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(443); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function max(comparer) { @@ -54075,15 +54230,15 @@ function max(comparer) { /***/ }), -/* 442 */ +/* 443 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return reduce; }); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(443); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(438); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(415); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(444); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(439); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(416); /* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(24); /** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ @@ -54104,7 +54259,7 @@ function reduce(accumulator, seed) { /***/ }), -/* 443 */ +/* 444 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54186,7 +54341,7 @@ var ScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 444 */ +/* 445 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54206,7 +54361,7 @@ function merge() { /***/ }), -/* 445 */ +/* 446 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54231,7 +54386,7 @@ function mergeMapTo(innerObservable, resultSelector, concurrent) { /***/ }), -/* 446 */ +/* 447 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54346,13 +54501,13 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 447 */ +/* 448 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "min", function() { return min; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(442); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(443); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function min(comparer) { @@ -54365,7 +54520,7 @@ function min(comparer) { /***/ }), -/* 448 */ +/* 449 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54414,7 +54569,7 @@ var MulticastOperator = /*@__PURE__*/ (function () { /***/ }), -/* 449 */ +/* 450 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54508,7 +54663,7 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 450 */ +/* 451 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54556,7 +54711,7 @@ var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 451 */ +/* 452 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54579,7 +54734,7 @@ function partition(predicate, thisArg) { /***/ }), -/* 452 */ +/* 453 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54619,14 +54774,14 @@ function plucker(props, length) { /***/ }), -/* 453 */ +/* 454 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return publish; }); /* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(27); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(448); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(449); /** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ @@ -54639,14 +54794,14 @@ function publish(selector) { /***/ }), -/* 454 */ +/* 455 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return publishBehavior; }); /* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(32); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(448); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(449); /** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ @@ -54657,14 +54812,14 @@ function publishBehavior(value) { /***/ }), -/* 455 */ +/* 456 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return publishLast; }); /* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(50); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(448); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(449); /** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ @@ -54675,14 +54830,14 @@ function publishLast() { /***/ }), -/* 456 */ +/* 457 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return publishReplay; }); /* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(33); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(448); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(449); /** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ @@ -54698,7 +54853,7 @@ function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) { /***/ }), -/* 457 */ +/* 458 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54725,7 +54880,7 @@ function race() { /***/ }), -/* 458 */ +/* 459 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54790,7 +54945,7 @@ var RepeatSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 459 */ +/* 460 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54886,7 +55041,7 @@ var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 460 */ +/* 461 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54939,7 +55094,7 @@ var RetrySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 461 */ +/* 462 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55027,7 +55182,7 @@ var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 462 */ +/* 463 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55084,7 +55239,7 @@ var SampleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 463 */ +/* 464 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55144,7 +55299,7 @@ function dispatchNotification(state) { /***/ }), -/* 464 */ +/* 465 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55267,13 +55422,13 @@ var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 465 */ +/* 466 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "share", function() { return share; }); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(448); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(449); /* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30); /* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27); /** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */ @@ -55290,7 +55445,7 @@ function share() { /***/ }), -/* 466 */ +/* 467 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55355,7 +55510,7 @@ function shareReplayOperator(_a) { /***/ }), -/* 467 */ +/* 468 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55435,7 +55590,7 @@ var SingleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 468 */ +/* 469 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55477,7 +55632,7 @@ var SkipSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 469 */ +/* 470 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55539,7 +55694,7 @@ var SkipLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 470 */ +/* 471 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55600,7 +55755,7 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 471 */ +/* 472 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55656,7 +55811,7 @@ var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 472 */ +/* 473 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55685,13 +55840,13 @@ function startWith() { /***/ }), -/* 473 */ +/* 474 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return subscribeOn; }); -/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(474); +/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(475); /** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ function subscribeOn(scheduler, delay) { @@ -55716,7 +55871,7 @@ var SubscribeOnOperator = /*@__PURE__*/ (function () { /***/ }), -/* 474 */ +/* 475 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55780,13 +55935,13 @@ var SubscribeOnObservable = /*@__PURE__*/ (function (_super) { /***/ }), -/* 475 */ +/* 476 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return switchAll; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(476); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(477); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25); /** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ @@ -55798,7 +55953,7 @@ function switchAll() { /***/ }), -/* 476 */ +/* 477 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55892,13 +56047,13 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 477 */ +/* 478 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return switchMapTo; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(476); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(477); /** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ function switchMapTo(innerObservable, resultSelector) { @@ -55908,7 +56063,7 @@ function switchMapTo(innerObservable, resultSelector) { /***/ }), -/* 478 */ +/* 479 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55958,7 +56113,7 @@ var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 479 */ +/* 480 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56026,7 +56181,7 @@ var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 480 */ +/* 481 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56114,7 +56269,7 @@ var TapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 481 */ +/* 482 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56218,7 +56373,7 @@ var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 482 */ +/* 483 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56227,7 +56382,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); -/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(481); +/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(482); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ @@ -56316,7 +56471,7 @@ function dispatchNext(arg) { /***/ }), -/* 483 */ +/* 484 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56324,7 +56479,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return timeInterval; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeInterval", function() { return TimeInterval; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(443); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(444); /* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(90); /* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(66); /** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ @@ -56360,7 +56515,7 @@ var TimeInterval = /*@__PURE__*/ (function () { /***/ }), -/* 484 */ +/* 485 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56368,7 +56523,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return timeout; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); /* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(64); -/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(485); +/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(486); /* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(49); /** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */ @@ -56385,7 +56540,7 @@ function timeout(due, scheduler) { /***/ }), -/* 485 */ +/* 486 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56393,7 +56548,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return timeoutWith; }); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(417); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(418); /* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(69); /* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(70); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -56467,7 +56622,7 @@ var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 486 */ +/* 487 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56497,13 +56652,13 @@ var Timestamp = /*@__PURE__*/ (function () { /***/ }), -/* 487 */ +/* 488 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return toArray; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(442); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(443); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function toArrayReducer(arr, item, index) { @@ -56520,7 +56675,7 @@ function toArray() { /***/ }), -/* 488 */ +/* 489 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56600,7 +56755,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 489 */ +/* 490 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56690,7 +56845,7 @@ var WindowCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 490 */ +/* 491 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56860,7 +57015,7 @@ function dispatchWindowClose(state) { /***/ }), -/* 491 */ +/* 492 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57003,7 +57158,7 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 492 */ +/* 493 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57100,7 +57255,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 493 */ +/* 494 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57195,7 +57350,7 @@ var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 494 */ +/* 495 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57217,7 +57372,7 @@ function zip() { /***/ }), -/* 495 */ +/* 496 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57233,7 +57388,7 @@ function zipAll(project) { /***/ }), -/* 496 */ +/* 497 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57242,8 +57397,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(162); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(145); -/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(497); -/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(498); +/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(498); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(499); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -57325,7 +57480,7 @@ function toArray(value) { } /***/ }), -/* 497 */ +/* 498 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57478,7 +57633,7 @@ function addProjectToTree(tree, pathParts, project) { } /***/ }), -/* 498 */ +/* 499 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57486,12 +57641,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; }); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(499); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(500); /* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(369); +/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(370); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(145); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(280); +/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(283); +/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(145); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(280); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -57521,6 +57677,7 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope + /** * Helper class for dealing with a set of projects as children of * the Kibana project. The kbn/pm is currently implemented to be @@ -57535,7 +57692,7 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope class Kibana { static async loadFrom(rootPath) { - return new Kibana(await Object(_projects__WEBPACK_IMPORTED_MODULE_3__["getProjects"])(rootPath, Object(_config__WEBPACK_IMPORTED_MODULE_4__["getProjectPaths"])({ + return new Kibana(await Object(_projects__WEBPACK_IMPORTED_MODULE_4__["getProjects"])(rootPath, Object(_config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"])({ rootPath }))); } @@ -57594,7 +57751,7 @@ class Kibana { getProjectAndDeps(name) { const project = this.getProject(name); - return Object(_projects__WEBPACK_IMPORTED_MODULE_3__["includeTransitiveProjects"])([project], this.allWorkspaceProjects); + return Object(_projects__WEBPACK_IMPORTED_MODULE_4__["includeTransitiveProjects"])([project], this.allWorkspaceProjects); } /** filter the projects to just those matching certain paths/include/exclude tags */ @@ -57603,7 +57760,7 @@ class Kibana { const allProjects = this.getAllProjects(); const filteredProjects = new Map(); const pkgJsonPaths = Array.from(allProjects.values()).map(p => p.packageJsonLocation); - const filteredPkgJsonGlobs = Object(_config__WEBPACK_IMPORTED_MODULE_4__["getProjectPaths"])(_objectSpread(_objectSpread({}, options), {}, { + const filteredPkgJsonGlobs = Object(_config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"])(_objectSpread(_objectSpread({}, options), {}, { rootPath: this.kibanaProject.path })).map(g => path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(g, 'package.json')); const matchingPkgJsonPaths = multimatch__WEBPACK_IMPORTED_MODULE_1___default()(pkgJsonPaths, filteredPkgJsonGlobs); @@ -57629,18 +57786,38 @@ class Kibana { return !this.isPartOfRepo(project); } + resolveAllProductionDependencies(yarnLock, log) { + const kibanaDeps = Object(_yarn_lock__WEBPACK_IMPORTED_MODULE_3__["resolveDepsForProject"])({ + project: this.kibanaProject, + yarnLock, + kbn: this, + includeDependentProject: true, + productionDepsOnly: true, + log + }); + const xpackDeps = Object(_yarn_lock__WEBPACK_IMPORTED_MODULE_3__["resolveDepsForProject"])({ + project: this.getProject('x-pack'), + yarnLock, + kbn: this, + includeDependentProject: true, + productionDepsOnly: true, + log + }); + return new Map([...kibanaDeps.entries(), ...xpackDeps.entries()]); + } + } /***/ }), -/* 499 */ +/* 500 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const minimatch = __webpack_require__(149); -const arrayUnion = __webpack_require__(500); -const arrayDiffer = __webpack_require__(501); -const arrify = __webpack_require__(502); +const arrayUnion = __webpack_require__(501); +const arrayDiffer = __webpack_require__(502); +const arrify = __webpack_require__(503); module.exports = (list, patterns, options = {}) => { list = arrify(list); @@ -57664,7 +57841,7 @@ module.exports = (list, patterns, options = {}) => { /***/ }), -/* 500 */ +/* 501 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57676,7 +57853,7 @@ module.exports = (...arguments_) => { /***/ }), -/* 501 */ +/* 502 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57691,7 +57868,7 @@ module.exports = arrayDiffer; /***/ }), -/* 502 */ +/* 503 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57721,12 +57898,12 @@ module.exports = arrify; /***/ }), -/* 503 */ +/* 504 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(504); +/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(505); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); /* @@ -57750,15 +57927,15 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 504 */ +/* 505 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return buildProductionProjects; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(505); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(506); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(288); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(289); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); @@ -57898,7 +58075,7 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { } /***/ }), -/* 505 */ +/* 506 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57906,13 +58083,13 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { const EventEmitter = __webpack_require__(155); const path = __webpack_require__(4); const os = __webpack_require__(120); -const pAll = __webpack_require__(506); -const arrify = __webpack_require__(508); -const globby = __webpack_require__(509); -const isGlob = __webpack_require__(719); -const cpFile = __webpack_require__(720); -const junk = __webpack_require__(732); -const CpyError = __webpack_require__(733); +const pAll = __webpack_require__(507); +const arrify = __webpack_require__(509); +const globby = __webpack_require__(510); +const isGlob = __webpack_require__(720); +const cpFile = __webpack_require__(721); +const junk = __webpack_require__(733); +const CpyError = __webpack_require__(734); const defaultOptions = { ignoreJunk: true @@ -58031,12 +58208,12 @@ module.exports = (source, destination, { /***/ }), -/* 506 */ +/* 507 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(507); +const pMap = __webpack_require__(508); module.exports = (iterable, options) => pMap(iterable, element => element(), options); // TODO: Remove this for the next major release @@ -58044,7 +58221,7 @@ module.exports.default = module.exports; /***/ }), -/* 507 */ +/* 508 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58123,7 +58300,7 @@ module.exports.default = pMap; /***/ }), -/* 508 */ +/* 509 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58153,17 +58330,17 @@ module.exports = arrify; /***/ }), -/* 509 */ +/* 510 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(133); -const arrayUnion = __webpack_require__(510); +const arrayUnion = __webpack_require__(511); const glob = __webpack_require__(146); -const fastGlob = __webpack_require__(512); -const dirGlob = __webpack_require__(712); -const gitignore = __webpack_require__(715); +const fastGlob = __webpack_require__(513); +const dirGlob = __webpack_require__(713); +const gitignore = __webpack_require__(716); const DEFAULT_FILTER = () => false; @@ -58308,12 +58485,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 510 */ +/* 511 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(511); +var arrayUniq = __webpack_require__(512); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -58321,7 +58498,7 @@ module.exports = function () { /***/ }), -/* 511 */ +/* 512 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58390,10 +58567,10 @@ if ('Set' in global) { /***/ }), -/* 512 */ +/* 513 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(513); +const pkg = __webpack_require__(514); module.exports = pkg.async; module.exports.default = pkg.async; @@ -58406,19 +58583,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 513 */ +/* 514 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(514); -var taskManager = __webpack_require__(515); -var reader_async_1 = __webpack_require__(683); -var reader_stream_1 = __webpack_require__(707); -var reader_sync_1 = __webpack_require__(708); -var arrayUtils = __webpack_require__(710); -var streamUtils = __webpack_require__(711); +var optionsManager = __webpack_require__(515); +var taskManager = __webpack_require__(516); +var reader_async_1 = __webpack_require__(684); +var reader_stream_1 = __webpack_require__(708); +var reader_sync_1 = __webpack_require__(709); +var arrayUtils = __webpack_require__(711); +var streamUtils = __webpack_require__(712); /** * Synchronous API. */ @@ -58484,7 +58661,7 @@ function isString(source) { /***/ }), -/* 514 */ +/* 515 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58522,13 +58699,13 @@ exports.prepare = prepare; /***/ }), -/* 515 */ +/* 516 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(516); +var patternUtils = __webpack_require__(517); /** * Generate tasks based on parent directory of each pattern. */ @@ -58619,16 +58796,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 516 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var globParent = __webpack_require__(517); -var isGlob = __webpack_require__(520); -var micromatch = __webpack_require__(521); +var globParent = __webpack_require__(518); +var isGlob = __webpack_require__(521); +var micromatch = __webpack_require__(522); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -58774,15 +58951,15 @@ exports.matchAny = matchAny; /***/ }), -/* 517 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(4); -var isglob = __webpack_require__(518); -var pathDirname = __webpack_require__(519); +var isglob = __webpack_require__(519); +var pathDirname = __webpack_require__(520); var isWin32 = __webpack_require__(120).platform() === 'win32'; module.exports = function globParent(str) { @@ -58805,7 +58982,7 @@ module.exports = function globParent(str) { /***/ }), -/* 518 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -58815,7 +58992,7 @@ module.exports = function globParent(str) { * Licensed under the MIT License. */ -var isExtglob = __webpack_require__(302); +var isExtglob = __webpack_require__(303); module.exports = function isGlob(str) { if (typeof str !== 'string' || str === '') { @@ -58836,7 +59013,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 519 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58986,7 +59163,7 @@ module.exports.win32 = win32; /***/ }), -/* 520 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -58996,7 +59173,7 @@ module.exports.win32 = win32; * Released under the MIT License. */ -var isExtglob = __webpack_require__(302); +var isExtglob = __webpack_require__(303); var chars = { '{': '}', '(': ')', '[': ']'}; module.exports = function isGlob(str, options) { @@ -59038,7 +59215,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 521 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59049,18 +59226,18 @@ module.exports = function isGlob(str, options) { */ var util = __webpack_require__(111); -var braces = __webpack_require__(522); -var toRegex = __webpack_require__(635); -var extend = __webpack_require__(643); +var braces = __webpack_require__(523); +var toRegex = __webpack_require__(636); +var extend = __webpack_require__(644); /** * Local dependencies */ -var compilers = __webpack_require__(646); -var parsers = __webpack_require__(679); -var cache = __webpack_require__(680); -var utils = __webpack_require__(681); +var compilers = __webpack_require__(647); +var parsers = __webpack_require__(680); +var cache = __webpack_require__(681); +var utils = __webpack_require__(682); var MAX_LENGTH = 1024 * 64; /** @@ -59922,7 +60099,7 @@ module.exports = micromatch; /***/ }), -/* 522 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59932,18 +60109,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(523); -var unique = __webpack_require__(537); -var extend = __webpack_require__(532); +var toRegex = __webpack_require__(524); +var unique = __webpack_require__(538); +var extend = __webpack_require__(533); /** * Local dependencies */ -var compilers = __webpack_require__(538); -var parsers = __webpack_require__(555); -var Braces = __webpack_require__(565); -var utils = __webpack_require__(539); +var compilers = __webpack_require__(539); +var parsers = __webpack_require__(556); +var Braces = __webpack_require__(566); +var utils = __webpack_require__(540); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -60247,15 +60424,15 @@ module.exports = braces; /***/ }), -/* 523 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(524); -var extend = __webpack_require__(532); -var not = __webpack_require__(534); +var define = __webpack_require__(525); +var extend = __webpack_require__(533); +var not = __webpack_require__(535); var MAX_LENGTH = 1024 * 64; /** @@ -60402,7 +60579,7 @@ module.exports.makeRe = makeRe; /***/ }), -/* 524 */ +/* 525 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60415,7 +60592,7 @@ module.exports.makeRe = makeRe; -var isDescriptor = __webpack_require__(525); +var isDescriptor = __webpack_require__(526); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -60440,7 +60617,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 525 */ +/* 526 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60453,9 +60630,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(526); -var isAccessor = __webpack_require__(527); -var isData = __webpack_require__(530); +var typeOf = __webpack_require__(527); +var isAccessor = __webpack_require__(528); +var isData = __webpack_require__(531); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -60469,7 +60646,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 526 */ +/* 527 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -60622,7 +60799,7 @@ function isBuffer(val) { /***/ }), -/* 527 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60635,7 +60812,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(528); +var typeOf = __webpack_require__(529); // accessor descriptor properties var accessor = { @@ -60698,10 +60875,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 528 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(529); +var isBuffer = __webpack_require__(530); var toString = Object.prototype.toString; /** @@ -60820,7 +60997,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 529 */ +/* 530 */ /***/ (function(module, exports) { /*! @@ -60847,7 +61024,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 530 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60860,7 +61037,7 @@ function isSlowBuffer (obj) { -var typeOf = __webpack_require__(531); +var typeOf = __webpack_require__(532); // data descriptor properties var data = { @@ -60909,10 +61086,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 531 */ +/* 532 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(529); +var isBuffer = __webpack_require__(530); var toString = Object.prototype.toString; /** @@ -61031,13 +61208,13 @@ module.exports = function kindOf(val) { /***/ }), -/* 532 */ +/* 533 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(533); +var isObject = __webpack_require__(534); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -61071,7 +61248,7 @@ function hasOwn(obj, key) { /***/ }), -/* 533 */ +/* 534 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61091,13 +61268,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 534 */ +/* 535 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(535); +var extend = __webpack_require__(536); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -61164,13 +61341,13 @@ module.exports = toRegex; /***/ }), -/* 535 */ +/* 536 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(536); +var isObject = __webpack_require__(537); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -61204,7 +61381,7 @@ function hasOwn(obj, key) { /***/ }), -/* 536 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61224,7 +61401,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 537 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61274,13 +61451,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 538 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(539); +var utils = __webpack_require__(540); module.exports = function(braces, options) { braces.compiler @@ -61563,25 +61740,25 @@ function hasQueue(node) { /***/ }), -/* 539 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(540); +var splitString = __webpack_require__(541); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(532); -utils.flatten = __webpack_require__(546); -utils.isObject = __webpack_require__(544); -utils.fillRange = __webpack_require__(547); -utils.repeat = __webpack_require__(554); -utils.unique = __webpack_require__(537); +utils.extend = __webpack_require__(533); +utils.flatten = __webpack_require__(547); +utils.isObject = __webpack_require__(545); +utils.fillRange = __webpack_require__(548); +utils.repeat = __webpack_require__(555); +utils.unique = __webpack_require__(538); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -61913,7 +62090,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 540 */ +/* 541 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61926,7 +62103,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(541); +var extend = __webpack_require__(542); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -62091,14 +62268,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 541 */ +/* 542 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(542); -var assignSymbols = __webpack_require__(545); +var isExtendable = __webpack_require__(543); +var assignSymbols = __webpack_require__(546); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -62158,7 +62335,7 @@ function isEnum(obj, key) { /***/ }), -/* 542 */ +/* 543 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62171,7 +62348,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(543); +var isPlainObject = __webpack_require__(544); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -62179,7 +62356,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 543 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62192,7 +62369,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(544); +var isObject = __webpack_require__(545); function isObjectObject(o) { return isObject(o) === true @@ -62223,7 +62400,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 544 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62242,7 +62419,7 @@ module.exports = function isObject(val) { /***/ }), -/* 545 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62289,7 +62466,7 @@ module.exports = function(receiver, objects) { /***/ }), -/* 546 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62318,7 +62495,7 @@ function flat(arr, res) { /***/ }), -/* 547 */ +/* 548 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62332,10 +62509,10 @@ function flat(arr, res) { var util = __webpack_require__(111); -var isNumber = __webpack_require__(548); -var extend = __webpack_require__(550); -var repeat = __webpack_require__(552); -var toRegex = __webpack_require__(553); +var isNumber = __webpack_require__(549); +var extend = __webpack_require__(551); +var repeat = __webpack_require__(553); +var toRegex = __webpack_require__(554); /** * Return a range of numbers or letters. @@ -62533,7 +62710,7 @@ module.exports = fillRange; /***/ }), -/* 548 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62546,7 +62723,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(549); +var typeOf = __webpack_require__(550); module.exports = function isNumber(num) { var type = typeOf(num); @@ -62562,10 +62739,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 549 */ +/* 550 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(529); +var isBuffer = __webpack_require__(530); var toString = Object.prototype.toString; /** @@ -62684,13 +62861,13 @@ module.exports = function kindOf(val) { /***/ }), -/* 550 */ +/* 551 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(551); +var isObject = __webpack_require__(552); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -62724,7 +62901,7 @@ function hasOwn(obj, key) { /***/ }), -/* 551 */ +/* 552 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62744,7 +62921,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 552 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62821,7 +62998,7 @@ function repeat(str, num) { /***/ }), -/* 553 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62834,8 +63011,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(552); -var isNumber = __webpack_require__(548); +var repeat = __webpack_require__(553); +var isNumber = __webpack_require__(549); var cache = {}; function toRegexRange(min, max, options) { @@ -63122,7 +63299,7 @@ module.exports = toRegexRange; /***/ }), -/* 554 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63147,14 +63324,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 555 */ +/* 556 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(556); -var utils = __webpack_require__(539); +var Node = __webpack_require__(557); +var utils = __webpack_require__(540); /** * Braces parsers @@ -63514,15 +63691,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 556 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(544); -var define = __webpack_require__(557); -var utils = __webpack_require__(564); +var isObject = __webpack_require__(545); +var define = __webpack_require__(558); +var utils = __webpack_require__(565); var ownNames; /** @@ -64013,7 +64190,7 @@ exports = module.exports = Node; /***/ }), -/* 557 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64026,7 +64203,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(558); +var isDescriptor = __webpack_require__(559); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -64051,7 +64228,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 558 */ +/* 559 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64064,9 +64241,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(559); -var isAccessor = __webpack_require__(560); -var isData = __webpack_require__(562); +var typeOf = __webpack_require__(560); +var isAccessor = __webpack_require__(561); +var isData = __webpack_require__(563); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -64080,7 +64257,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 559 */ +/* 560 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -64215,7 +64392,7 @@ function isBuffer(val) { /***/ }), -/* 560 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64228,7 +64405,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(561); +var typeOf = __webpack_require__(562); // accessor descriptor properties var accessor = { @@ -64291,7 +64468,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 561 */ +/* 562 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -64426,7 +64603,7 @@ function isBuffer(val) { /***/ }), -/* 562 */ +/* 563 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64439,7 +64616,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(563); +var typeOf = __webpack_require__(564); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -64482,7 +64659,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 563 */ +/* 564 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -64617,13 +64794,13 @@ function isBuffer(val) { /***/ }), -/* 564 */ +/* 565 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(549); +var typeOf = __webpack_require__(550); var utils = module.exports; /** @@ -65643,17 +65820,17 @@ function assert(val, message) { /***/ }), -/* 565 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(532); -var Snapdragon = __webpack_require__(566); -var compilers = __webpack_require__(538); -var parsers = __webpack_require__(555); -var utils = __webpack_require__(539); +var extend = __webpack_require__(533); +var Snapdragon = __webpack_require__(567); +var compilers = __webpack_require__(539); +var parsers = __webpack_require__(556); +var utils = __webpack_require__(540); /** * Customize Snapdragon parser and renderer @@ -65754,17 +65931,17 @@ module.exports = Braces; /***/ }), -/* 566 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(567); -var define = __webpack_require__(593); -var Compiler = __webpack_require__(603); -var Parser = __webpack_require__(632); -var utils = __webpack_require__(612); +var Base = __webpack_require__(568); +var define = __webpack_require__(594); +var Compiler = __webpack_require__(604); +var Parser = __webpack_require__(633); +var utils = __webpack_require__(613); var regexCache = {}; var cache = {}; @@ -65935,20 +66112,20 @@ module.exports.Parser = Parser; /***/ }), -/* 567 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(111); -var define = __webpack_require__(568); -var CacheBase = __webpack_require__(569); -var Emitter = __webpack_require__(570); -var isObject = __webpack_require__(544); -var merge = __webpack_require__(587); -var pascal = __webpack_require__(590); -var cu = __webpack_require__(591); +var define = __webpack_require__(569); +var CacheBase = __webpack_require__(570); +var Emitter = __webpack_require__(571); +var isObject = __webpack_require__(545); +var merge = __webpack_require__(588); +var pascal = __webpack_require__(591); +var cu = __webpack_require__(592); /** * Optionally define a custom `cache` namespace to use. @@ -66377,7 +66554,7 @@ module.exports.namespace = namespace; /***/ }), -/* 568 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66390,7 +66567,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(558); +var isDescriptor = __webpack_require__(559); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -66415,21 +66592,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 569 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(544); -var Emitter = __webpack_require__(570); -var visit = __webpack_require__(571); -var toPath = __webpack_require__(574); -var union = __webpack_require__(575); -var del = __webpack_require__(579); -var get = __webpack_require__(577); -var has = __webpack_require__(584); -var set = __webpack_require__(578); +var isObject = __webpack_require__(545); +var Emitter = __webpack_require__(571); +var visit = __webpack_require__(572); +var toPath = __webpack_require__(575); +var union = __webpack_require__(576); +var del = __webpack_require__(580); +var get = __webpack_require__(578); +var has = __webpack_require__(585); +var set = __webpack_require__(579); /** * Create a `Cache` constructor that when instantiated will @@ -66683,7 +66860,7 @@ module.exports.namespace = namespace; /***/ }), -/* 570 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { @@ -66852,7 +67029,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 571 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66865,8 +67042,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(572); -var mapVisit = __webpack_require__(573); +var visit = __webpack_require__(573); +var mapVisit = __webpack_require__(574); module.exports = function(collection, method, val) { var result; @@ -66889,7 +67066,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 572 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66902,7 +67079,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(544); +var isObject = __webpack_require__(545); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -66929,14 +67106,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 573 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(111); -var visit = __webpack_require__(572); +var visit = __webpack_require__(573); /** * Map `visit` over an array of objects. @@ -66973,7 +67150,7 @@ function isObject(val) { /***/ }), -/* 574 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66986,7 +67163,7 @@ function isObject(val) { -var typeOf = __webpack_require__(549); +var typeOf = __webpack_require__(550); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -67013,16 +67190,16 @@ function filter(arr) { /***/ }), -/* 575 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(536); -var union = __webpack_require__(576); -var get = __webpack_require__(577); -var set = __webpack_require__(578); +var isObject = __webpack_require__(537); +var union = __webpack_require__(577); +var get = __webpack_require__(578); +var set = __webpack_require__(579); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -67050,7 +67227,7 @@ function arrayify(val) { /***/ }), -/* 576 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67086,7 +67263,7 @@ module.exports = function union(init) { /***/ }), -/* 577 */ +/* 578 */ /***/ (function(module, exports) { /*! @@ -67142,7 +67319,7 @@ function toString(val) { /***/ }), -/* 578 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67155,10 +67332,10 @@ function toString(val) { -var split = __webpack_require__(540); -var extend = __webpack_require__(535); -var isPlainObject = __webpack_require__(543); -var isObject = __webpack_require__(536); +var split = __webpack_require__(541); +var extend = __webpack_require__(536); +var isPlainObject = __webpack_require__(544); +var isObject = __webpack_require__(537); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -67204,7 +67381,7 @@ function isValidKey(key) { /***/ }), -/* 579 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67217,8 +67394,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(544); -var has = __webpack_require__(580); +var isObject = __webpack_require__(545); +var has = __webpack_require__(581); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -67243,7 +67420,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 580 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67256,9 +67433,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(581); -var hasValues = __webpack_require__(583); -var get = __webpack_require__(577); +var isObject = __webpack_require__(582); +var hasValues = __webpack_require__(584); +var get = __webpack_require__(578); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -67269,7 +67446,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 581 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67282,7 +67459,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(582); +var isArray = __webpack_require__(583); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -67290,7 +67467,7 @@ module.exports = function isObject(val) { /***/ }), -/* 582 */ +/* 583 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -67301,7 +67478,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 583 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67344,7 +67521,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 584 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67357,9 +67534,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(544); -var hasValues = __webpack_require__(585); -var get = __webpack_require__(577); +var isObject = __webpack_require__(545); +var hasValues = __webpack_require__(586); +var get = __webpack_require__(578); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -67367,7 +67544,7 @@ module.exports = function(val, prop) { /***/ }), -/* 585 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67380,8 +67557,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(586); -var isNumber = __webpack_require__(548); +var typeOf = __webpack_require__(587); +var isNumber = __webpack_require__(549); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -67434,10 +67611,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 586 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(529); +var isBuffer = __webpack_require__(530); var toString = Object.prototype.toString; /** @@ -67559,14 +67736,14 @@ module.exports = function kindOf(val) { /***/ }), -/* 587 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(588); -var forIn = __webpack_require__(589); +var isExtendable = __webpack_require__(589); +var forIn = __webpack_require__(590); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -67630,7 +67807,7 @@ module.exports = mixinDeep; /***/ }), -/* 588 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67643,7 +67820,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(543); +var isPlainObject = __webpack_require__(544); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -67651,7 +67828,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 589 */ +/* 590 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67674,7 +67851,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 590 */ +/* 591 */ /***/ (function(module, exports) { /*! @@ -67701,14 +67878,14 @@ module.exports = pascalcase; /***/ }), -/* 591 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(111); -var utils = __webpack_require__(592); +var utils = __webpack_require__(593); /** * Expose class utils @@ -68073,7 +68250,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 592 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68087,10 +68264,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(576); -utils.define = __webpack_require__(593); -utils.isObj = __webpack_require__(544); -utils.staticExtend = __webpack_require__(600); +utils.union = __webpack_require__(577); +utils.define = __webpack_require__(594); +utils.isObj = __webpack_require__(545); +utils.staticExtend = __webpack_require__(601); /** @@ -68101,7 +68278,7 @@ module.exports = utils; /***/ }), -/* 593 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68114,7 +68291,7 @@ module.exports = utils; -var isDescriptor = __webpack_require__(594); +var isDescriptor = __webpack_require__(595); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -68139,7 +68316,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 594 */ +/* 595 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68152,9 +68329,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(595); -var isAccessor = __webpack_require__(596); -var isData = __webpack_require__(598); +var typeOf = __webpack_require__(596); +var isAccessor = __webpack_require__(597); +var isData = __webpack_require__(599); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -68168,7 +68345,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 595 */ +/* 596 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -68321,7 +68498,7 @@ function isBuffer(val) { /***/ }), -/* 596 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68334,7 +68511,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(597); +var typeOf = __webpack_require__(598); // accessor descriptor properties var accessor = { @@ -68397,10 +68574,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 597 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(529); +var isBuffer = __webpack_require__(530); var toString = Object.prototype.toString; /** @@ -68519,7 +68696,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 598 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68532,7 +68709,7 @@ module.exports = function kindOf(val) { -var typeOf = __webpack_require__(599); +var typeOf = __webpack_require__(600); // data descriptor properties var data = { @@ -68581,10 +68758,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 599 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(529); +var isBuffer = __webpack_require__(530); var toString = Object.prototype.toString; /** @@ -68703,7 +68880,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 600 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68716,8 +68893,8 @@ module.exports = function kindOf(val) { -var copy = __webpack_require__(601); -var define = __webpack_require__(593); +var copy = __webpack_require__(602); +var define = __webpack_require__(594); var util = __webpack_require__(111); /** @@ -68800,15 +68977,15 @@ module.exports = extend; /***/ }), -/* 601 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(549); -var copyDescriptor = __webpack_require__(602); -var define = __webpack_require__(593); +var typeOf = __webpack_require__(550); +var copyDescriptor = __webpack_require__(603); +var define = __webpack_require__(594); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -68981,7 +69158,7 @@ module.exports.has = has; /***/ }), -/* 602 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69069,16 +69246,16 @@ function isObject(val) { /***/ }), -/* 603 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(604); -var define = __webpack_require__(593); -var debug = __webpack_require__(606)('snapdragon:compiler'); -var utils = __webpack_require__(612); +var use = __webpack_require__(605); +var define = __webpack_require__(594); +var debug = __webpack_require__(607)('snapdragon:compiler'); +var utils = __webpack_require__(613); /** * Create a new `Compiler` with the given `options`. @@ -69232,7 +69409,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(631); + var sourcemaps = __webpack_require__(632); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -69253,7 +69430,7 @@ module.exports = Compiler; /***/ }), -/* 604 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69266,7 +69443,7 @@ module.exports = Compiler; -var utils = __webpack_require__(605); +var utils = __webpack_require__(606); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -69381,7 +69558,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 605 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69395,8 +69572,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(593); -utils.isObject = __webpack_require__(544); +utils.define = __webpack_require__(594); +utils.isObject = __webpack_require__(545); utils.isString = function(val) { @@ -69411,7 +69588,7 @@ module.exports = utils; /***/ }), -/* 606 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -69420,14 +69597,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(607); + module.exports = __webpack_require__(608); } else { - module.exports = __webpack_require__(610); + module.exports = __webpack_require__(611); } /***/ }), -/* 607 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -69436,7 +69613,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(608); +exports = module.exports = __webpack_require__(609); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -69618,7 +69795,7 @@ function localstorage() { /***/ }), -/* 608 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { @@ -69634,7 +69811,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(609); +exports.humanize = __webpack_require__(610); /** * The currently active debug mode names, and names to skip. @@ -69826,7 +70003,7 @@ function coerce(val) { /***/ }), -/* 609 */ +/* 610 */ /***/ (function(module, exports) { /** @@ -69984,7 +70161,7 @@ function plural(ms, n, name) { /***/ }), -/* 610 */ +/* 611 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -70000,7 +70177,7 @@ var util = __webpack_require__(111); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(608); +exports = module.exports = __webpack_require__(609); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -70179,7 +70356,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(611); + var net = __webpack_require__(612); stream = new net.Socket({ fd: fd, readable: false, @@ -70238,13 +70415,13 @@ exports.enable(load()); /***/ }), -/* 611 */ +/* 612 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 612 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70254,9 +70431,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(535); -exports.SourceMap = __webpack_require__(613); -exports.sourceMapResolve = __webpack_require__(624); +exports.extend = __webpack_require__(536); +exports.SourceMap = __webpack_require__(614); +exports.sourceMapResolve = __webpack_require__(625); /** * Convert backslash in the given string to forward slashes @@ -70299,7 +70476,7 @@ exports.last = function(arr, n) { /***/ }), -/* 613 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -70307,13 +70484,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(614).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(620).SourceMapConsumer; -exports.SourceNode = __webpack_require__(623).SourceNode; +exports.SourceMapGenerator = __webpack_require__(615).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(621).SourceMapConsumer; +exports.SourceNode = __webpack_require__(624).SourceNode; /***/ }), -/* 614 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -70323,10 +70500,10 @@ exports.SourceNode = __webpack_require__(623).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(615); -var util = __webpack_require__(617); -var ArraySet = __webpack_require__(618).ArraySet; -var MappingList = __webpack_require__(619).MappingList; +var base64VLQ = __webpack_require__(616); +var util = __webpack_require__(618); +var ArraySet = __webpack_require__(619).ArraySet; +var MappingList = __webpack_require__(620).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -70735,7 +70912,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 615 */ +/* 616 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -70775,7 +70952,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(616); +var base64 = __webpack_require__(617); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -70881,7 +71058,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 616 */ +/* 617 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -70954,7 +71131,7 @@ exports.decode = function (charCode) { /***/ }), -/* 617 */ +/* 618 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71377,7 +71554,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 618 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71387,7 +71564,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(617); +var util = __webpack_require__(618); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -71504,7 +71681,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 619 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71514,7 +71691,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(617); +var util = __webpack_require__(618); /** * Determine whether mappingB is after mappingA with respect to generated @@ -71589,7 +71766,7 @@ exports.MappingList = MappingList; /***/ }), -/* 620 */ +/* 621 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71599,11 +71776,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(617); -var binarySearch = __webpack_require__(621); -var ArraySet = __webpack_require__(618).ArraySet; -var base64VLQ = __webpack_require__(615); -var quickSort = __webpack_require__(622).quickSort; +var util = __webpack_require__(618); +var binarySearch = __webpack_require__(622); +var ArraySet = __webpack_require__(619).ArraySet; +var base64VLQ = __webpack_require__(616); +var quickSort = __webpack_require__(623).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -72677,7 +72854,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 621 */ +/* 622 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72794,7 +72971,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 622 */ +/* 623 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72914,7 +73091,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 623 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72924,8 +73101,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(614).SourceMapGenerator; -var util = __webpack_require__(617); +var SourceMapGenerator = __webpack_require__(615).SourceMapGenerator; +var util = __webpack_require__(618); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -73333,17 +73510,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 624 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(625) -var resolveUrl = __webpack_require__(626) -var decodeUriComponent = __webpack_require__(627) -var urix = __webpack_require__(629) -var atob = __webpack_require__(630) +var sourceMappingURL = __webpack_require__(626) +var resolveUrl = __webpack_require__(627) +var decodeUriComponent = __webpack_require__(628) +var urix = __webpack_require__(630) +var atob = __webpack_require__(631) @@ -73641,7 +73818,7 @@ module.exports = { /***/ }), -/* 625 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -73704,7 +73881,7 @@ void (function(root, factory) { /***/ }), -/* 626 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -73722,13 +73899,13 @@ module.exports = resolveUrl /***/ }), -/* 627 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(628) +var decodeUriComponent = __webpack_require__(629) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -73739,7 +73916,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 628 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73840,7 +74017,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 629 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -73863,7 +74040,7 @@ module.exports = urix /***/ }), -/* 630 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73877,7 +74054,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 631 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73885,8 +74062,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(133); var path = __webpack_require__(4); -var define = __webpack_require__(593); -var utils = __webpack_require__(612); +var define = __webpack_require__(594); +var utils = __webpack_require__(613); /** * Expose `mixin()`. @@ -74029,19 +74206,19 @@ exports.comment = function(node) { /***/ }), -/* 632 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(604); +var use = __webpack_require__(605); var util = __webpack_require__(111); -var Cache = __webpack_require__(633); -var define = __webpack_require__(593); -var debug = __webpack_require__(606)('snapdragon:parser'); -var Position = __webpack_require__(634); -var utils = __webpack_require__(612); +var Cache = __webpack_require__(634); +var define = __webpack_require__(594); +var debug = __webpack_require__(607)('snapdragon:parser'); +var Position = __webpack_require__(635); +var utils = __webpack_require__(613); /** * Create a new `Parser` with the given `input` and `options`. @@ -74569,7 +74746,7 @@ module.exports = Parser; /***/ }), -/* 633 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74676,13 +74853,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 634 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(593); +var define = __webpack_require__(594); /** * Store position for a node @@ -74697,16 +74874,16 @@ module.exports = function Position(start, parser) { /***/ }), -/* 635 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(636); -var define = __webpack_require__(642); -var extend = __webpack_require__(643); -var not = __webpack_require__(645); +var safe = __webpack_require__(637); +var define = __webpack_require__(643); +var extend = __webpack_require__(644); +var not = __webpack_require__(646); var MAX_LENGTH = 1024 * 64; /** @@ -74859,10 +75036,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 636 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(637); +var parse = __webpack_require__(638); var types = parse.types; module.exports = function (re, opts) { @@ -74908,13 +75085,13 @@ function isRegExp (x) { /***/ }), -/* 637 */ +/* 638 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(638); -var types = __webpack_require__(639); -var sets = __webpack_require__(640); -var positions = __webpack_require__(641); +var util = __webpack_require__(639); +var types = __webpack_require__(640); +var sets = __webpack_require__(641); +var positions = __webpack_require__(642); module.exports = function(regexpStr) { @@ -75196,11 +75373,11 @@ module.exports.types = types; /***/ }), -/* 638 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(639); -var sets = __webpack_require__(640); +var types = __webpack_require__(640); +var sets = __webpack_require__(641); // All of these are private and only used by randexp. @@ -75313,7 +75490,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 639 */ +/* 640 */ /***/ (function(module, exports) { module.exports = { @@ -75329,10 +75506,10 @@ module.exports = { /***/ }), -/* 640 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(639); +var types = __webpack_require__(640); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -75417,10 +75594,10 @@ exports.anyChar = function() { /***/ }), -/* 641 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(639); +var types = __webpack_require__(640); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -75440,7 +75617,7 @@ exports.end = function() { /***/ }), -/* 642 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75453,8 +75630,8 @@ exports.end = function() { -var isobject = __webpack_require__(544); -var isDescriptor = __webpack_require__(558); +var isobject = __webpack_require__(545); +var isDescriptor = __webpack_require__(559); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -75485,14 +75662,14 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 643 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(644); -var assignSymbols = __webpack_require__(545); +var isExtendable = __webpack_require__(645); +var assignSymbols = __webpack_require__(546); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -75552,7 +75729,7 @@ function isEnum(obj, key) { /***/ }), -/* 644 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75565,7 +75742,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(543); +var isPlainObject = __webpack_require__(544); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -75573,14 +75750,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 645 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(643); -var safe = __webpack_require__(636); +var extend = __webpack_require__(644); +var safe = __webpack_require__(637); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -75652,14 +75829,14 @@ module.exports = toRegex; /***/ }), -/* 646 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(647); -var extglob = __webpack_require__(663); +var nanomatch = __webpack_require__(648); +var extglob = __webpack_require__(664); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -75736,7 +75913,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 647 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75747,17 +75924,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(111); -var toRegex = __webpack_require__(648); -var extend = __webpack_require__(649); +var toRegex = __webpack_require__(649); +var extend = __webpack_require__(650); /** * Local dependencies */ -var compilers = __webpack_require__(651); -var parsers = __webpack_require__(652); -var cache = __webpack_require__(655); -var utils = __webpack_require__(657); +var compilers = __webpack_require__(652); +var parsers = __webpack_require__(653); +var cache = __webpack_require__(656); +var utils = __webpack_require__(658); var MAX_LENGTH = 1024 * 64; /** @@ -76581,15 +76758,15 @@ module.exports = nanomatch; /***/ }), -/* 648 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(593); -var extend = __webpack_require__(535); -var not = __webpack_require__(534); +var define = __webpack_require__(594); +var extend = __webpack_require__(536); +var not = __webpack_require__(535); var MAX_LENGTH = 1024 * 64; /** @@ -76736,14 +76913,14 @@ module.exports.makeRe = makeRe; /***/ }), -/* 649 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(650); -var assignSymbols = __webpack_require__(545); +var isExtendable = __webpack_require__(651); +var assignSymbols = __webpack_require__(546); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -76803,7 +76980,7 @@ function isEnum(obj, key) { /***/ }), -/* 650 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76816,7 +76993,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(543); +var isPlainObject = __webpack_require__(544); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -76824,7 +77001,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 651 */ +/* 652 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77170,15 +77347,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 652 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(534); -var toRegex = __webpack_require__(648); -var isOdd = __webpack_require__(653); +var regexNot = __webpack_require__(535); +var toRegex = __webpack_require__(649); +var isOdd = __webpack_require__(654); /** * Characters to use in negation regex (we want to "not" match @@ -77564,7 +77741,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 653 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77577,7 +77754,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(654); +var isNumber = __webpack_require__(655); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -77591,7 +77768,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 654 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77619,14 +77796,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 655 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(656))(); +module.exports = new (__webpack_require__(657))(); /***/ }), -/* 656 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77639,7 +77816,7 @@ module.exports = new (__webpack_require__(656))(); -var MapCache = __webpack_require__(633); +var MapCache = __webpack_require__(634); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -77761,7 +77938,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 657 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77774,14 +77951,14 @@ var path = __webpack_require__(4); * Module dependencies */ -var isWindows = __webpack_require__(658)(); -var Snapdragon = __webpack_require__(566); -utils.define = __webpack_require__(659); -utils.diff = __webpack_require__(660); -utils.extend = __webpack_require__(649); -utils.pick = __webpack_require__(661); -utils.typeOf = __webpack_require__(662); -utils.unique = __webpack_require__(537); +var isWindows = __webpack_require__(659)(); +var Snapdragon = __webpack_require__(567); +utils.define = __webpack_require__(660); +utils.diff = __webpack_require__(661); +utils.extend = __webpack_require__(650); +utils.pick = __webpack_require__(662); +utils.typeOf = __webpack_require__(663); +utils.unique = __webpack_require__(538); /** * Returns true if the given value is effectively an empty string @@ -78147,7 +78324,7 @@ utils.unixify = function(options) { /***/ }), -/* 658 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -78175,7 +78352,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 659 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78188,8 +78365,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(544); -var isDescriptor = __webpack_require__(558); +var isobject = __webpack_require__(545); +var isDescriptor = __webpack_require__(559); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -78220,7 +78397,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 660 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78274,7 +78451,7 @@ function diffArray(one, two) { /***/ }), -/* 661 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78287,7 +78464,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(544); +var isObject = __webpack_require__(545); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -78316,7 +78493,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 662 */ +/* 663 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -78451,7 +78628,7 @@ function isBuffer(val) { /***/ }), -/* 663 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78461,18 +78638,18 @@ function isBuffer(val) { * Module dependencies */ -var extend = __webpack_require__(535); -var unique = __webpack_require__(537); -var toRegex = __webpack_require__(648); +var extend = __webpack_require__(536); +var unique = __webpack_require__(538); +var toRegex = __webpack_require__(649); /** * Local dependencies */ -var compilers = __webpack_require__(664); -var parsers = __webpack_require__(675); -var Extglob = __webpack_require__(678); -var utils = __webpack_require__(677); +var compilers = __webpack_require__(665); +var parsers = __webpack_require__(676); +var Extglob = __webpack_require__(679); +var utils = __webpack_require__(678); var MAX_LENGTH = 1024 * 64; /** @@ -78789,13 +78966,13 @@ module.exports = extglob; /***/ }), -/* 664 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(665); +var brackets = __webpack_require__(666); /** * Extglob compilers @@ -78965,7 +79142,7 @@ module.exports = function(extglob) { /***/ }), -/* 665 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78975,17 +79152,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(666); -var parsers = __webpack_require__(668); +var compilers = __webpack_require__(667); +var parsers = __webpack_require__(669); /** * Module dependencies */ -var debug = __webpack_require__(670)('expand-brackets'); -var extend = __webpack_require__(535); -var Snapdragon = __webpack_require__(566); -var toRegex = __webpack_require__(648); +var debug = __webpack_require__(671)('expand-brackets'); +var extend = __webpack_require__(536); +var Snapdragon = __webpack_require__(567); +var toRegex = __webpack_require__(649); /** * Parses the given POSIX character class `pattern` and returns a @@ -79183,13 +79360,13 @@ module.exports = brackets; /***/ }), -/* 666 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(667); +var posix = __webpack_require__(668); module.exports = function(brackets) { brackets.compiler @@ -79277,7 +79454,7 @@ module.exports = function(brackets) { /***/ }), -/* 667 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79306,14 +79483,14 @@ module.exports = { /***/ }), -/* 668 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(669); -var define = __webpack_require__(593); +var utils = __webpack_require__(670); +var define = __webpack_require__(594); /** * Text regex @@ -79532,14 +79709,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 669 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(648); -var regexNot = __webpack_require__(534); +var toRegex = __webpack_require__(649); +var regexNot = __webpack_require__(535); var cached; /** @@ -79573,7 +79750,7 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 670 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -79582,14 +79759,14 @@ exports.createRegex = function(pattern, include) { */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(671); + module.exports = __webpack_require__(672); } else { - module.exports = __webpack_require__(674); + module.exports = __webpack_require__(675); } /***/ }), -/* 671 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -79598,7 +79775,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(672); +exports = module.exports = __webpack_require__(673); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -79780,7 +79957,7 @@ function localstorage() { /***/ }), -/* 672 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { @@ -79796,7 +79973,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(673); +exports.humanize = __webpack_require__(674); /** * The currently active debug mode names, and names to skip. @@ -79988,7 +80165,7 @@ function coerce(val) { /***/ }), -/* 673 */ +/* 674 */ /***/ (function(module, exports) { /** @@ -80146,7 +80323,7 @@ function plural(ms, n, name) { /***/ }), -/* 674 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -80162,7 +80339,7 @@ var util = __webpack_require__(111); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(672); +exports = module.exports = __webpack_require__(673); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -80341,7 +80518,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(611); + var net = __webpack_require__(612); stream = new net.Socket({ fd: fd, readable: false, @@ -80400,15 +80577,15 @@ exports.enable(load()); /***/ }), -/* 675 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(665); -var define = __webpack_require__(676); -var utils = __webpack_require__(677); +var brackets = __webpack_require__(666); +var define = __webpack_require__(677); +var utils = __webpack_require__(678); /** * Characters to use in text regex (we want to "not" match @@ -80563,7 +80740,7 @@ module.exports = parsers; /***/ }), -/* 676 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80576,7 +80753,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(558); +var isDescriptor = __webpack_require__(559); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -80601,14 +80778,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 677 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(534); -var Cache = __webpack_require__(656); +var regex = __webpack_require__(535); +var Cache = __webpack_require__(657); /** * Utils @@ -80677,7 +80854,7 @@ utils.createRegex = function(str) { /***/ }), -/* 678 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80687,16 +80864,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(566); -var define = __webpack_require__(676); -var extend = __webpack_require__(535); +var Snapdragon = __webpack_require__(567); +var define = __webpack_require__(677); +var extend = __webpack_require__(536); /** * Local dependencies */ -var compilers = __webpack_require__(664); -var parsers = __webpack_require__(675); +var compilers = __webpack_require__(665); +var parsers = __webpack_require__(676); /** * Customize Snapdragon parser and renderer @@ -80762,16 +80939,16 @@ module.exports = Extglob; /***/ }), -/* 679 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(663); -var nanomatch = __webpack_require__(647); -var regexNot = __webpack_require__(534); -var toRegex = __webpack_require__(635); +var extglob = __webpack_require__(664); +var nanomatch = __webpack_require__(648); +var regexNot = __webpack_require__(535); +var toRegex = __webpack_require__(636); var not; /** @@ -80852,14 +81029,14 @@ function textRegex(pattern) { /***/ }), -/* 680 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(656))(); +module.exports = new (__webpack_require__(657))(); /***/ }), -/* 681 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80872,13 +81049,13 @@ var path = __webpack_require__(4); * Module dependencies */ -var Snapdragon = __webpack_require__(566); -utils.define = __webpack_require__(642); -utils.diff = __webpack_require__(660); -utils.extend = __webpack_require__(643); -utils.pick = __webpack_require__(661); -utils.typeOf = __webpack_require__(682); -utils.unique = __webpack_require__(537); +var Snapdragon = __webpack_require__(567); +utils.define = __webpack_require__(643); +utils.diff = __webpack_require__(661); +utils.extend = __webpack_require__(644); +utils.pick = __webpack_require__(662); +utils.typeOf = __webpack_require__(683); +utils.unique = __webpack_require__(538); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -81175,7 +81352,7 @@ utils.unixify = function(options) { /***/ }), -/* 682 */ +/* 683 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -81310,7 +81487,7 @@ function isBuffer(val) { /***/ }), -/* 683 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81329,9 +81506,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(684); -var reader_1 = __webpack_require__(697); -var fs_stream_1 = __webpack_require__(701); +var readdir = __webpack_require__(685); +var reader_1 = __webpack_require__(698); +var fs_stream_1 = __webpack_require__(702); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -81392,15 +81569,15 @@ exports.default = ReaderAsync; /***/ }), -/* 684 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(685); -const readdirAsync = __webpack_require__(693); -const readdirStream = __webpack_require__(696); +const readdirSync = __webpack_require__(686); +const readdirAsync = __webpack_require__(694); +const readdirStream = __webpack_require__(697); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -81484,7 +81661,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 685 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81492,11 +81669,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(686); +const DirectoryReader = __webpack_require__(687); let syncFacade = { - fs: __webpack_require__(691), - forEach: __webpack_require__(692), + fs: __webpack_require__(692), + forEach: __webpack_require__(693), sync: true }; @@ -81525,7 +81702,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 686 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81534,9 +81711,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(137).Readable; const EventEmitter = __webpack_require__(155).EventEmitter; const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(687); -const stat = __webpack_require__(689); -const call = __webpack_require__(690); +const normalizeOptions = __webpack_require__(688); +const stat = __webpack_require__(690); +const call = __webpack_require__(691); /** * Asynchronously reads the contents of a directory and streams the results @@ -81912,14 +82089,14 @@ module.exports = DirectoryReader; /***/ }), -/* 687 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(688); +const globToRegExp = __webpack_require__(689); module.exports = normalizeOptions; @@ -82096,7 +82273,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 688 */ +/* 689 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -82233,13 +82410,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 689 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(690); +const call = __webpack_require__(691); module.exports = stat; @@ -82314,7 +82491,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 690 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82375,14 +82552,14 @@ function callOnce (fn) { /***/ }), -/* 691 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(133); -const call = __webpack_require__(690); +const call = __webpack_require__(691); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -82446,7 +82623,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 692 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82475,7 +82652,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 693 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82483,12 +82660,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(694); -const DirectoryReader = __webpack_require__(686); +const maybe = __webpack_require__(695); +const DirectoryReader = __webpack_require__(687); let asyncFacade = { fs: __webpack_require__(133), - forEach: __webpack_require__(695), + forEach: __webpack_require__(696), async: true }; @@ -82530,7 +82707,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 694 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82557,7 +82734,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 695 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82593,7 +82770,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 696 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82601,11 +82778,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(686); +const DirectoryReader = __webpack_require__(687); let streamFacade = { fs: __webpack_require__(133), - forEach: __webpack_require__(695), + forEach: __webpack_require__(696), async: true }; @@ -82625,16 +82802,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 697 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var deep_1 = __webpack_require__(698); -var entry_1 = __webpack_require__(700); -var pathUtil = __webpack_require__(699); +var deep_1 = __webpack_require__(699); +var entry_1 = __webpack_require__(701); +var pathUtil = __webpack_require__(700); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -82700,14 +82877,14 @@ exports.default = Reader; /***/ }), -/* 698 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(699); -var patternUtils = __webpack_require__(516); +var pathUtils = __webpack_require__(700); +var patternUtils = __webpack_require__(517); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -82790,7 +82967,7 @@ exports.default = DeepFilter; /***/ }), -/* 699 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82821,14 +82998,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 700 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(699); -var patternUtils = __webpack_require__(516); +var pathUtils = __webpack_require__(700); +var patternUtils = __webpack_require__(517); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -82913,7 +83090,7 @@ exports.default = EntryFilter; /***/ }), -/* 701 */ +/* 702 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82933,8 +83110,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(137); -var fsStat = __webpack_require__(702); -var fs_1 = __webpack_require__(706); +var fsStat = __webpack_require__(703); +var fs_1 = __webpack_require__(707); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -82984,14 +83161,14 @@ exports.default = FileSystemStream; /***/ }), -/* 702 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(703); -const statProvider = __webpack_require__(705); +const optionsManager = __webpack_require__(704); +const statProvider = __webpack_require__(706); /** * Asynchronous API. */ @@ -83022,13 +83199,13 @@ exports.statSync = statSync; /***/ }), -/* 703 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(704); +const fsAdapter = __webpack_require__(705); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -83041,7 +83218,7 @@ exports.prepare = prepare; /***/ }), -/* 704 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83064,7 +83241,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 705 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83116,7 +83293,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 706 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83147,7 +83324,7 @@ exports.default = FileSystem; /***/ }), -/* 707 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83167,9 +83344,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(137); -var readdir = __webpack_require__(684); -var reader_1 = __webpack_require__(697); -var fs_stream_1 = __webpack_require__(701); +var readdir = __webpack_require__(685); +var reader_1 = __webpack_require__(698); +var fs_stream_1 = __webpack_require__(702); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -83237,7 +83414,7 @@ exports.default = ReaderStream; /***/ }), -/* 708 */ +/* 709 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83256,9 +83433,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(684); -var reader_1 = __webpack_require__(697); -var fs_sync_1 = __webpack_require__(709); +var readdir = __webpack_require__(685); +var reader_1 = __webpack_require__(698); +var fs_sync_1 = __webpack_require__(710); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -83318,7 +83495,7 @@ exports.default = ReaderSync; /***/ }), -/* 709 */ +/* 710 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83337,8 +83514,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(702); -var fs_1 = __webpack_require__(706); +var fsStat = __webpack_require__(703); +var fs_1 = __webpack_require__(707); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -83384,7 +83561,7 @@ exports.default = FileSystemSync; /***/ }), -/* 710 */ +/* 711 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83400,13 +83577,13 @@ exports.flatten = flatten; /***/ }), -/* 711 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var merge2 = __webpack_require__(291); +var merge2 = __webpack_require__(292); /** * Merge multiple streams and propagate their errors into one stream in parallel. */ @@ -83421,13 +83598,13 @@ exports.merge = merge; /***/ }), -/* 712 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(713); +const pathType = __webpack_require__(714); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -83493,13 +83670,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 713 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(133); -const pify = __webpack_require__(714); +const pify = __webpack_require__(715); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -83542,7 +83719,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 714 */ +/* 715 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83633,17 +83810,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 715 */ +/* 716 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(133); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(512); -const gitIgnore = __webpack_require__(716); -const pify = __webpack_require__(717); -const slash = __webpack_require__(718); +const fastGlob = __webpack_require__(513); +const gitIgnore = __webpack_require__(717); +const pify = __webpack_require__(718); +const slash = __webpack_require__(719); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -83741,7 +83918,7 @@ module.exports.sync = options => { /***/ }), -/* 716 */ +/* 717 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -84210,7 +84387,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 717 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84285,7 +84462,7 @@ module.exports = (input, options) => { /***/ }), -/* 718 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84303,7 +84480,7 @@ module.exports = input => { /***/ }), -/* 719 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -84313,7 +84490,7 @@ module.exports = input => { * Released under the MIT License. */ -var isExtglob = __webpack_require__(302); +var isExtglob = __webpack_require__(303); var chars = { '{': '}', '(': ')', '[': ']'}; var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; @@ -84357,17 +84534,17 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 720 */ +/* 721 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(133); -const pEvent = __webpack_require__(721); -const CpFileError = __webpack_require__(724); -const fs = __webpack_require__(728); -const ProgressEmitter = __webpack_require__(731); +const pEvent = __webpack_require__(722); +const CpFileError = __webpack_require__(725); +const fs = __webpack_require__(729); +const ProgressEmitter = __webpack_require__(732); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -84481,12 +84658,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 721 */ +/* 722 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(722); +const pTimeout = __webpack_require__(723); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -84777,12 +84954,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 722 */ +/* 723 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(723); +const pFinally = __webpack_require__(724); class TimeoutError extends Error { constructor(message) { @@ -84828,7 +85005,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 723 */ +/* 724 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84850,12 +85027,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 724 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(725); +const NestedError = __webpack_require__(726); class CpFileError extends NestedError { constructor(message, nested) { @@ -84869,10 +85046,10 @@ module.exports = CpFileError; /***/ }), -/* 725 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { -var inherits = __webpack_require__(726); +var inherits = __webpack_require__(727); var NestedError = function (message, nested) { this.nested = nested; @@ -84923,7 +85100,7 @@ module.exports = NestedError; /***/ }), -/* 726 */ +/* 727 */ /***/ (function(module, exports, __webpack_require__) { try { @@ -84931,12 +85108,12 @@ try { if (typeof util.inherits !== 'function') throw ''; module.exports = util.inherits; } catch (e) { - module.exports = __webpack_require__(727); + module.exports = __webpack_require__(728); } /***/ }), -/* 727 */ +/* 728 */ /***/ (function(module, exports) { if (typeof Object.create === 'function') { @@ -84965,16 +85142,16 @@ if (typeof Object.create === 'function') { /***/ }), -/* 728 */ +/* 729 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(111); const fs = __webpack_require__(132); -const makeDir = __webpack_require__(729); -const pEvent = __webpack_require__(721); -const CpFileError = __webpack_require__(724); +const makeDir = __webpack_require__(730); +const pEvent = __webpack_require__(722); +const CpFileError = __webpack_require__(725); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -85071,7 +85248,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 729 */ +/* 730 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85079,7 +85256,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(133); const path = __webpack_require__(4); const {promisify} = __webpack_require__(111); -const semver = __webpack_require__(730); +const semver = __webpack_require__(731); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -85234,7 +85411,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 730 */ +/* 731 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -86836,7 +87013,7 @@ function coerce (version, options) { /***/ }), -/* 731 */ +/* 732 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86877,7 +87054,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 732 */ +/* 733 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86923,12 +87100,12 @@ exports.default = module.exports; /***/ }), -/* 733 */ +/* 734 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(734); +const NestedError = __webpack_require__(735); class CpyError extends NestedError { constructor(message, nested) { @@ -86942,7 +87119,7 @@ module.exports = CpyError; /***/ }), -/* 734 */ +/* 735 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(111).inherits; diff --git a/packages/kbn-pm/src/commands/bootstrap.test.ts b/packages/kbn-pm/src/commands/bootstrap.test.ts index 97505a66a1fff..956c4e72933ba 100644 --- a/packages/kbn-pm/src/commands/bootstrap.test.ts +++ b/packages/kbn-pm/src/commands/bootstrap.test.ts @@ -19,6 +19,7 @@ jest.mock('../utils/scripts'); jest.mock('../utils/link_project_executables'); +jest.mock('../utils/validate_yarn_lock'); import { resolve } from 'path'; diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index a559f9a20432a..7cf89c5f08f96 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -25,6 +25,8 @@ import { Project } from '../utils/project'; import { ICommand } from './'; import { getAllChecksums } from '../utils/project_checksums'; import { BootstrapCacheFile } from '../utils/bootstrap_cache_file'; +import { readYarnLock } from '../utils/yarn_lock'; +import { validateYarnLock } from '../utils/validate_yarn_lock'; export const BootstrapCommand: ICommand = { description: 'Install dependencies and crosslink projects', @@ -54,6 +56,10 @@ export const BootstrapCommand: ICommand = { } } + const yarnLock = await readYarnLock(kbn); + + await validateYarnLock(kbn, yarnLock); + await linkProjectExecutables(projects, projectGraph); /** @@ -63,7 +69,7 @@ export const BootstrapCommand: ICommand = { * have to, as it will slow down the bootstrapping process. */ - const checksums = await getAllChecksums(kbn, log); + const checksums = await getAllChecksums(kbn, log, yarnLock); const caches = new Map(); let cachedProjectCount = 0; diff --git a/packages/kbn-pm/src/utils/__snapshots__/link_project_executables.test.ts.snap b/packages/kbn-pm/src/utils/__snapshots__/link_project_executables.test.ts.snap index 5bda7b544e201..311e350f6e865 100644 --- a/packages/kbn-pm/src/utils/__snapshots__/link_project_executables.test.ts.snap +++ b/packages/kbn-pm/src/utils/__snapshots__/link_project_executables.test.ts.snap @@ -18,6 +18,7 @@ Object { "mkdirp": Array [], "readFile": Array [], "unlink": Array [], + "writeFile": Array [], } `; @@ -66,5 +67,6 @@ Object { ], "readFile": Array [], "unlink": Array [], + "writeFile": Array [], } `; diff --git a/packages/kbn-pm/src/utils/fs.ts b/packages/kbn-pm/src/utils/fs.ts index 44fc59bdeba96..21fd2c32b9c7b 100644 --- a/packages/kbn-pm/src/utils/fs.ts +++ b/packages/kbn-pm/src/utils/fs.ts @@ -25,6 +25,7 @@ import { promisify } from 'util'; const lstat = promisify(fs.lstat); export const readFile = promisify(fs.readFile); +export const writeFile = promisify(fs.writeFile); const symlink = promisify(fs.symlink); export const chmod = promisify(fs.chmod); const cmdShim = promisify(cmdShimCb); diff --git a/packages/kbn-pm/src/utils/kibana.ts b/packages/kbn-pm/src/utils/kibana.ts index 7fca4bd01822b..e48b61611d63f 100644 --- a/packages/kbn-pm/src/utils/kibana.ts +++ b/packages/kbn-pm/src/utils/kibana.ts @@ -22,6 +22,8 @@ import Path from 'path'; import multimatch from 'multimatch'; import isPathInside from 'is-path-inside'; +import { resolveDepsForProject, YarnLock } from './yarn_lock'; +import { Log } from './log'; import { ProjectMap, getProjects, includeTransitiveProjects } from './projects'; import { Project } from './project'; import { getProjectPaths } from '../config'; @@ -133,4 +135,26 @@ export class Kibana { isOutsideRepo(project: Project) { return !this.isPartOfRepo(project); } + + resolveAllProductionDependencies(yarnLock: YarnLock, log: Log) { + const kibanaDeps = resolveDepsForProject({ + project: this.kibanaProject, + yarnLock, + kbn: this, + includeDependentProject: true, + productionDepsOnly: true, + log, + })!; + + const xpackDeps = resolveDepsForProject({ + project: this.getProject('x-pack')!, + yarnLock, + kbn: this, + includeDependentProject: true, + productionDepsOnly: true, + log, + })!; + + return new Map([...kibanaDeps.entries(), ...xpackDeps.entries()]); + } } diff --git a/packages/kbn-pm/src/utils/project_checksums.ts b/packages/kbn-pm/src/utils/project_checksums.ts index 839c44f4f18c4..c13788c89bfaa 100644 --- a/packages/kbn-pm/src/utils/project_checksums.ts +++ b/packages/kbn-pm/src/utils/project_checksums.ts @@ -24,7 +24,7 @@ import { promisify } from 'util'; import execa from 'execa'; -import { readYarnLock, YarnLock } from './yarn_lock'; +import { YarnLock, resolveDepsForProject } from './yarn_lock'; import { ProjectMap } from '../utils/projects'; import { Project } from '../utils/project'; import { Kibana } from '../utils/kibana'; @@ -145,51 +145,6 @@ async function getLatestSha(project: Project, kbn: Kibana) { return stdout.trim() || undefined; } -/** - * Get a list of the absolute dependencies of this project, as resolved - * in the yarn.lock file, does not include other projects in the workspace - * or their dependencies - */ -function resolveDepsForProject(project: Project, yarnLock: YarnLock, kbn: Kibana, log: Log) { - /** map of [name@range, name@resolved] */ - const resolved = new Map(); - - const queue: Array<[string, string]> = Object.entries(project.allDependencies); - - while (queue.length) { - const [name, versionRange] = queue.shift()!; - const req = `${name}@${versionRange}`; - - if (resolved.has(req)) { - continue; - } - - if (!kbn.hasProject(name)) { - const pkg = yarnLock[req]; - if (!pkg) { - log.warning( - 'yarn.lock file is out of date, please run `yarn kbn bootstrap` to re-enable caching' - ); - return; - } - - const res = `${name}@${pkg.version}`; - resolved.set(req, res); - - const allDepsEntries = [ - ...Object.entries(pkg.dependencies || {}), - ...Object.entries(pkg.optionalDependencies || {}), - ]; - - for (const [childName, childVersionRange] of allDepsEntries) { - queue.push([childName, childVersionRange]); - } - } - } - - return Array.from(resolved.values()).sort((a, b) => a.localeCompare(b)); -} - /** * Get the checksum for a specific project in the workspace */ @@ -224,11 +179,22 @@ async function getChecksum( }) ); - const deps = await resolveDepsForProject(project, yarnLock, kbn, log); - if (!deps) { + const depMap = resolveDepsForProject({ + project, + yarnLock, + kbn, + log, + includeDependentProject: false, + productionDepsOnly: false, + }); + if (!depMap) { return; } + const deps = Array.from(depMap.values()) + .map(({ name, version }) => `${name}@${version}`) + .sort((a, b) => a.localeCompare(b)); + log.verbose(`[${project.name}] resolved %d deps`, deps.length); const checksum = JSON.stringify( @@ -256,10 +222,9 @@ async function getChecksum( * - un-committed changes * - resolved dependencies from yarn.lock referenced by project package.json */ -export async function getAllChecksums(kbn: Kibana, log: Log) { +export async function getAllChecksums(kbn: Kibana, log: Log, yarnLock: YarnLock) { const projects = kbn.getAllProjects(); const changesByProject = await getChangesForProjects(projects, kbn, log); - const yarnLock = await readYarnLock(kbn); /** map of [project.name, cacheKey] */ const cacheKeys: ChecksumMap = new Map(); diff --git a/packages/kbn-pm/src/utils/validate_yarn_lock.ts b/packages/kbn-pm/src/utils/validate_yarn_lock.ts new file mode 100644 index 0000000000000..e110dc4d921cf --- /dev/null +++ b/packages/kbn-pm/src/utils/validate_yarn_lock.ts @@ -0,0 +1,99 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// @ts-expect-error published types are useless +import { stringify as stringifyLockfile } from '@yarnpkg/lockfile'; +import dedent from 'dedent'; + +import { writeFile } from './fs'; +import { Kibana } from './kibana'; +import { YarnLock } from './yarn_lock'; +import { log } from './log'; + +export async function validateYarnLock(kbn: Kibana, yarnLock: YarnLock) { + // look through all of the packages in the yarn.lock file to see if + // we have accidentally installed multiple lodash v4 versions + const lodash4Versions = new Set(); + const lodash4Reqs = new Set(); + for (const [req, dep] of Object.entries(yarnLock)) { + if (req.startsWith('lodash@') && dep.version.startsWith('4.')) { + lodash4Reqs.add(req); + lodash4Versions.add(dep.version); + } + } + + // if we find more than one lodash v4 version installed then delete + // lodash v4 requests from the yarn.lock file and prompt the user to + // retry bootstrap so that a single v4 version will be installed + if (lodash4Versions.size > 1) { + for (const req of lodash4Reqs) { + delete yarnLock[req]; + } + + await writeFile(kbn.getAbsolute('yarn.lock'), stringifyLockfile(yarnLock), 'utf8'); + + log.error(dedent` + + Multiple version of lodash v4 were detected, so they have been removed + from the yarn.lock file. Please rerun yarn kbn bootstrap to coalese the + lodash versions installed. + + If you still see this error when you re-bootstrap then you might need + to force a new dependency to use the latest version of lodash via the + "resolutions" field in package.json. + + If you have questions about this please reach out to the operations team. + + `); + + process.exit(1); + } + + // look through all the dependencies of production packages and production + // dependencies of those packages to determine if we're shipping any versions + // of lodash v3 in the distributable + const prodDependencies = kbn.resolveAllProductionDependencies(yarnLock, log); + const lodash3Versions = new Set(); + for (const dep of prodDependencies.values()) { + if (dep.name === 'lodash' && dep.version.startsWith('3.')) { + lodash3Versions.add(dep.version); + } + } + + // if any lodash v3 packages were found we abort and tell the user to fix things + if (lodash3Versions.size) { + log.error(dedent` + + Due to changes in the yarn.lock file and/or package.json files a version of + lodash 3 is now included in the production dependencies. To reduce the size of + our distributable and especially our front-end bundles we have decided to + prevent adding any new instances of lodash 3. + + Please inspect the changes to yarn.lock or package.json files to identify where + the lodash 3 version is coming from and remove it. + + If you have questions about this please reack out to the operations team. + + `); + + process.exit(1); + } + + log.success('yarn.lock analysis completed without any issues'); +} diff --git a/packages/kbn-pm/src/utils/yarn_lock.ts b/packages/kbn-pm/src/utils/yarn_lock.ts index f46240c0e1d2e..953341915e232 100644 --- a/packages/kbn-pm/src/utils/yarn_lock.ts +++ b/packages/kbn-pm/src/utils/yarn_lock.ts @@ -17,14 +17,16 @@ * under the License. */ -// @ts-ignore published types are worthless +// @ts-expect-error published types are worthless import { parse as parseLockfile } from '@yarnpkg/lockfile'; import { readFile } from '../utils/fs'; import { Kibana } from '../utils/kibana'; +import { Project } from '../utils/project'; +import { Log } from '../utils/log'; export interface YarnLock { - /** a simple map of version@versionrange tags to metadata about a package */ + /** a simple map of name@versionrange tags to metadata about a package */ [key: string]: { /** resolved version installed for this pacakge */ version: string; @@ -61,3 +63,82 @@ export async function readYarnLock(kbn: Kibana): Promise { return {}; } + +/** + * Get a list of the absolute dependencies of this project, as resolved + * in the yarn.lock file, does not include other projects in the workspace + * or their dependencies + */ +export function resolveDepsForProject({ + project: rootProject, + yarnLock, + kbn, + log, + productionDepsOnly, + includeDependentProject, +}: { + project: Project; + yarnLock: YarnLock; + kbn: Kibana; + log: Log; + productionDepsOnly: boolean; + includeDependentProject: boolean; +}) { + /** map of [name@range, { name, version }] */ + const resolved = new Map(); + + const seenProjects = new Set(); + const projectQueue: Project[] = [rootProject]; + const depQueue: Array<[string, string]> = []; + + while (projectQueue.length) { + const project = projectQueue.shift()!; + if (seenProjects.has(project)) { + continue; + } + seenProjects.add(project); + + const projectDeps = Object.entries( + productionDepsOnly ? project.productionDependencies : project.allDependencies + ); + for (const [name, versionRange] of projectDeps) { + depQueue.push([name, versionRange]); + } + + while (depQueue.length) { + const [name, versionRange] = depQueue.shift()!; + const req = `${name}@${versionRange}`; + + if (resolved.has(req)) { + continue; + } + + if (includeDependentProject && kbn.hasProject(name)) { + projectQueue.push(kbn.getProject(name)!); + } + + if (!kbn.hasProject(name)) { + const pkg = yarnLock[req]; + if (!pkg) { + log.warning( + 'yarn.lock file is out of date, please run `yarn kbn bootstrap` to re-enable caching' + ); + return; + } + + resolved.set(req, { name, version: pkg.version }); + + const allDepsEntries = [ + ...Object.entries(pkg.dependencies || {}), + ...Object.entries(pkg.optionalDependencies || {}), + ]; + + for (const [childName, childVersionRange] of allDepsEntries) { + depQueue.push([childName, childVersionRange]); + } + } + } + } + + return resolved; +} diff --git a/packages/kbn-std/src/index.ts b/packages/kbn-std/src/index.ts index 8cffcd43d7537..7cf70a0e28e2c 100644 --- a/packages/kbn-std/src/index.ts +++ b/packages/kbn-std/src/index.ts @@ -24,6 +24,6 @@ export { mapToObject } from './map_to_object'; export { merge } from './merge'; export { pick } from './pick'; export { withTimeout } from './promise'; -export { isRelativeUrl, modifyUrl, URLMeaningfulParts, ParsedQuery } from './url'; +export { isRelativeUrl, modifyUrl, URLMeaningfulParts } from './url'; export { unset } from './unset'; export { getFlattenedObject } from './get_flattened_object'; diff --git a/packages/kbn-std/src/merge.ts b/packages/kbn-std/src/merge.ts index c0de50544a34e..dca8077435657 100644 --- a/packages/kbn-std/src/merge.ts +++ b/packages/kbn-std/src/merge.ts @@ -16,6 +16,8 @@ * specific language governing permissions and limitations * under the License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isPlainObject from 'lodash/isPlainObject'; /** * Deeply merges two objects, omitting undefined values, and not deeply merging Arrays. diff --git a/packages/kbn-std/src/url.ts b/packages/kbn-std/src/url.ts index 7a0f08130816d..edcdebbd2bc81 100644 --- a/packages/kbn-std/src/url.ts +++ b/packages/kbn-std/src/url.ts @@ -18,11 +18,7 @@ */ import { format as formatUrl, parse as parseUrl, UrlObject } from 'url'; - -// duplicate type from 'query-string' to avoid adding the d.ts file to all packages depending on kbn-std -export interface ParsedQuery { - [key: string]: T | T[] | null | undefined; -} +import type { ParsedQuery } from 'query-string'; /** * We define our own typings because the current version of @types/node diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts index b238c5aa346ad..54983278726eb 100644 --- a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts @@ -75,11 +75,9 @@ export const parsedWorkingCollector: ParsedUsageCollection = [ type: 'StringKeyword', }, my_index_signature_prop: { - '': { - '@@INDEX@@': { - kind: SyntaxKind.NumberKeyword, - type: 'NumberKeyword', - }, + '@@INDEX@@': { + kind: SyntaxKind.NumberKeyword, + type: 'NumberKeyword', }, }, my_objects: { diff --git a/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap b/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap index 68b068b0cfe06..9868a7d31d498 100644 --- a/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap +++ b/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap @@ -96,16 +96,14 @@ Array [ "collectorName": "indexed_interface_with_not_matching_schema", "fetch": Object { "typeDescriptor": Object { - "": Object { - "@@INDEX@@": Object { - "count_1": Object { - "kind": 143, - "type": "NumberKeyword", - }, - "count_2": Object { - "kind": 143, - "type": "NumberKeyword", - }, + "@@INDEX@@": Object { + "count_1": Object { + "kind": 143, + "type": "NumberKeyword", + }, + "count_2": Object { + "kind": 143, + "type": "NumberKeyword", }, }, }, @@ -165,11 +163,9 @@ Array [ }, }, "my_index_signature_prop": Object { - "": Object { - "@@INDEX@@": Object { - "kind": 143, - "type": "NumberKeyword", - }, + "@@INDEX@@": Object { + "kind": 143, + "type": "NumberKeyword", }, }, "my_objects": Object { diff --git a/packages/kbn-telemetry-tools/src/tools/check_collector_integrity.ts b/packages/kbn-telemetry-tools/src/tools/check_collector_integrity.ts index 3205edb87aa29..8a5752f77d7fc 100644 --- a/packages/kbn-telemetry-tools/src/tools/check_collector_integrity.ts +++ b/packages/kbn-telemetry-tools/src/tools/check_collector_integrity.ts @@ -47,6 +47,7 @@ export function checkCompatibleTypeDescriptor( const typeDescriptorKinds = reduce( typeDescriptorTypes, (acc: any, type: number, key: string) => { + key = key.replace(/'/g, ''); try { acc[key] = kindToDescriptorName(type); } catch (err) { @@ -61,6 +62,7 @@ export function checkCompatibleTypeDescriptor( const transformedMappingKinds = reduce( schemaTypes, (acc: any, type: string, key: string) => { + key = key.replace(/'/g, ''); try { acc[key.replace(/.type$/, '.kind')] = compatibleSchemaTypes(type as any); } catch (err) { diff --git a/packages/kbn-telemetry-tools/src/tools/serializer.test.ts b/packages/kbn-telemetry-tools/src/tools/serializer.test.ts index 9475574a44219..6742117226368 100644 --- a/packages/kbn-telemetry-tools/src/tools/serializer.test.ts +++ b/packages/kbn-telemetry-tools/src/tools/serializer.test.ts @@ -44,13 +44,13 @@ export function loadFixtureProgram(fixtureName: string) { } describe('getDescriptor', () => { - const usageInterfaces = new Map(); + const usageInterfaces = new Map(); let tsProgram: ts.Program; beforeAll(() => { const { program, sourceFile } = loadFixtureProgram('constants'); tsProgram = program; for (const node of traverseNodes(sourceFile)) { - if (ts.isInterfaceDeclaration(node)) { + if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) { const interfaceName = node.name.getText(); usageInterfaces.set(interfaceName, node); } @@ -102,4 +102,26 @@ describe('getDescriptor', () => { 'Mapping does not support conflicting union types.' ); }); + + it('serializes TypeAliasDeclaration', () => { + const usageInterface = usageInterfaces.get('TypeAliasWithUnion')!; + const descriptor = getDescriptor(usageInterface, tsProgram); + expect(descriptor).toEqual({ + locale: { kind: ts.SyntaxKind.StringKeyword, type: 'StringKeyword' }, + prop1: { kind: ts.SyntaxKind.StringKeyword, type: 'StringKeyword' }, + prop2: { kind: ts.SyntaxKind.StringKeyword, type: 'StringKeyword' }, + prop3: { kind: ts.SyntaxKind.StringKeyword, type: 'StringKeyword' }, + prop4: { kind: ts.SyntaxKind.StringLiteral, type: 'StringLiteral' }, + prop5: { kind: ts.SyntaxKind.FirstLiteralToken, type: 'FirstLiteralToken' }, + }); + }); + + it('serializes Record entries', () => { + const usageInterface = usageInterfaces.get('TypeAliasWithRecord')!; + const descriptor = getDescriptor(usageInterface, tsProgram); + expect(descriptor).toEqual({ + locale: { kind: ts.SyntaxKind.StringKeyword, type: 'StringKeyword' }, + '@@INDEX@@': { kind: ts.SyntaxKind.NumberKeyword, type: 'NumberKeyword' }, + }); + }); }); diff --git a/packages/kbn-telemetry-tools/src/tools/serializer.ts b/packages/kbn-telemetry-tools/src/tools/serializer.ts index 7afe828298b4b..6fe02e3824ba7 100644 --- a/packages/kbn-telemetry-tools/src/tools/serializer.ts +++ b/packages/kbn-telemetry-tools/src/tools/serializer.ts @@ -79,9 +79,13 @@ export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor | } if (ts.isTypeLiteralNode(node) || ts.isInterfaceDeclaration(node)) { return node.members.reduce((acc, m) => { - acc[m.name?.getText() || ''] = getDescriptor(m, program); - return acc; - }, {} as any); + const key = m.name?.getText(); + if (key) { + return { ...acc, [key]: getDescriptor(m, program) }; + } else { + return { ...acc, ...getDescriptor(m, program) }; + } + }, {}); } // If it's defined as signature { [key: string]: OtherInterface } @@ -114,6 +118,10 @@ export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor | if (symbolName === 'Date') { return { kind: TelemetryKinds.Date, type: 'Date' }; } + // Support `Record` + if (symbolName === 'Record' && node.typeArguments![0].kind === ts.SyntaxKind.StringKeyword) { + return { '@@INDEX@@': getDescriptor(node.typeArguments![1], program) }; + } const declaration = (symbol?.getDeclarations() || [])[0]; if (declaration) { return getDescriptor(declaration, program); @@ -157,6 +165,19 @@ export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor | return uniqueKinds[0]; } + // Support `type MyUsageType = SomethingElse` + if (ts.isTypeAliasDeclaration(node)) { + return getDescriptor(node.type, program); + } + + // Support `&` unions + if (ts.isIntersectionTypeNode(node)) { + return node.types.reduce( + (acc, unionNode) => ({ ...acc, ...getDescriptor(unionNode, program) }), + {} + ); + } + switch (node.kind) { case ts.SyntaxKind.NumberKeyword: case ts.SyntaxKind.BooleanKeyword: diff --git a/packages/kbn-telemetry-tools/src/tools/utils.ts b/packages/kbn-telemetry-tools/src/tools/utils.ts index 3d6764117374c..e8e1b3fed1aef 100644 --- a/packages/kbn-telemetry-tools/src/tools/utils.ts +++ b/packages/kbn-telemetry-tools/src/tools/utils.ts @@ -249,7 +249,7 @@ export function difference(actual: any, expected: any) { function (result, value, key) { if (key && /@@INDEX@@/.test(`${key}`)) { // The type definition is an Index Signature, fuzzy searching for similar keys - const regexp = new RegExp(`${key}`.replace(/@@INDEX@@/g, '(.+)?')); + const regexp = new RegExp(`^${key}`.replace(/@@INDEX@@/g, '(.+)?')); const keysInBase = Object.keys(base) .map((k) => { const match = k.match(regexp); diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/entry.js index 365b84b83bd5f..69344174a2dc6 100644 --- a/packages/kbn-ui-shared-deps/entry.js +++ b/packages/kbn-ui-shared-deps/entry.js @@ -51,6 +51,8 @@ export const ElasticEui = require('@elastic/eui'); export const ElasticEuiLibServices = require('@elastic/eui/lib/services'); export const ElasticEuiLibServicesFormat = require('@elastic/eui/lib/services/format'); export const ElasticEuiChartsTheme = require('@elastic/eui/dist/eui_charts_theme'); +export const Lodash = require('lodash'); +export const LodashFp = require('lodash/fp'); import * as Theme from './theme.ts'; export { Theme }; diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index 84ca3435e02bc..a5d6954fd5cc0 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -62,5 +62,7 @@ exports.externals = { '@elastic/eui/dist/eui_charts_theme': '__kbnSharedDeps__.ElasticEuiChartsTheme', '@elastic/eui/dist/eui_theme_light.json': '__kbnSharedDeps__.Theme.euiLightVars', '@elastic/eui/dist/eui_theme_dark.json': '__kbnSharedDeps__.Theme.euiDarkVars', + lodash: '__kbnSharedDeps__.Lodash', + 'lodash/fp': '__kbnSharedDeps__.LodashFp', }; exports.publicPathLoader = require.resolve('./public_path_loader'); diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 372126c4418f5..278e8efd2d29e 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -20,6 +20,7 @@ "core-js": "^3.6.4", "custom-event-polyfill": "^0.3.0", "jquery": "^3.5.0", + "lodash": "^4.17.20", "mini-css-extract-plugin": "0.8.0", "moment": "^2.24.0", "moment-timezone": "^0.5.27", diff --git a/renovate.json5 b/renovate.json5 index 57d0fcb9f8ce2..17391c2f83827 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -27,31 +27,36 @@ ], }, separateMajorMinor: false, - masterIssue: false, + masterIssue: true, rangeStrategy: 'bump', + semanticCommits: false, + vulnerabilityAlerts: { + enabled: false, + }, npm: { lockFileMaintenance: { enabled: false, }, packageRules: [ - { - groupSlug: '@elastic/charts', - packageNames: ['@elastic/charts'], - reviewers: ['markov00'], - }, { packagePatterns: [ '.*', ], enabled: false, }, + { + groupName: '@elastic/charts', + packageNames: ['@elastic/charts'], + reviewers: ['markov00'], + enabled: true, + }, + { + groupName: 'vega related modules', + packageNames: ['vega', 'vega-lite', 'vega-schema-url-parser', 'vega-tooltip'], + reviewers: ['team:kibana-app'], + labels: ['Feature:Lens', 'Team:KibanaApp'], + enabled: true, + }, ], }, - prConcurrentLimit: 0, - vulnerabilityAlerts: { - enabled: false, - }, - rebaseStalePrs: false, - rebaseConflictedPrs: false, - semanticCommits: false, } diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index d8bd39b9dcdf4..a1715cf3dba2c 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -48,11 +48,6 @@ const CAN_CLUSTER = canRequire(CLUSTER_MANAGER_PATH); const REPL_PATH = resolve(__dirname, '../repl'); const CAN_REPL = canRequire(REPL_PATH); -// xpack is installed in both dev and the distributable, it's optional if -// install is a link to the source, not an actual install -const XPACK_DIR = resolve(__dirname, '../../../x-pack'); -const XPACK_INSTALLED = canRequire(XPACK_DIR); - const pathCollector = function () { const paths = []; return function (path) { @@ -137,16 +132,7 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { if (opts.logFile) set('logging.dest', opts.logFile); set('plugins.scanDirs', _.compact([].concat(get('plugins.scanDirs'), opts.pluginDir))); - set( - 'plugins.paths', - _.compact( - [].concat( - get('plugins.paths'), - opts.pluginPath, - XPACK_INSTALLED && !opts.oss ? [XPACK_DIR] : [] - ) - ) - ); + set('plugins.paths', _.compact([].concat(get('plugins.paths'), opts.pluginPath))); merge(extraCliOptions); merge(readKeystore()); diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index 5b51bc823d166..bd8c9e91f15a2 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -58,19 +58,6 @@ export interface InjectedMetadataParams { uiPlugins: InjectedPluginMetadata[]; anonymousStatusPage: boolean; legacyMetadata: { - app: { - id: string; - title: string; - }; - bundleId: string; - version: string; - branch: string; - buildNum: number; - buildSha: string; - basePath: string; - serverName: string; - devMode: boolean; - category?: AppCategory; uiSettings: { defaults: Record; user?: Record; @@ -167,18 +154,6 @@ export interface InjectedMetadataSetup { getPlugins: () => InjectedPluginMetadata[]; getAnonymousStatusPage: () => boolean; getLegacyMetadata: () => { - app: { - id: string; - title: string; - }; - bundleId: string; - version: string; - branch: string; - buildNum: number; - buildSha: string; - basePath: string; - serverName: string; - devMode: boolean; uiSettings: { defaults: Record; user?: Record | undefined; diff --git a/src/core/server/capabilities/capabilities_service.mock.ts b/src/core/server/capabilities/capabilities_service.mock.ts index 7d134f9592dc7..72824693705d9 100644 --- a/src/core/server/capabilities/capabilities_service.mock.ts +++ b/src/core/server/capabilities/capabilities_service.mock.ts @@ -18,6 +18,7 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; import { CapabilitiesService, CapabilitiesSetup, CapabilitiesStart } from './capabilities_service'; +import { Capabilities } from './types'; const createSetupContractMock = () => { const setupContract: jest.Mocked = { @@ -34,6 +35,14 @@ const createStartContractMock = () => { return setupContract; }; +const createCapabilitiesMock = (): Capabilities => { + return { + navLinks: {}, + management: {}, + catalogue: {}, + }; +}; + type CapabilitiesServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { @@ -47,4 +56,5 @@ export const capabilitiesServiceMock = { create: createMock, createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, + createCapabilities: createCapabilitiesMock, }; diff --git a/src/core/server/elasticsearch/client/mocks.ts b/src/core/server/elasticsearch/client/mocks.ts index 6fb3dc090bfb4..fb2826c787718 100644 --- a/src/core/server/elasticsearch/client/mocks.ts +++ b/src/core/server/elasticsearch/client/mocks.ts @@ -31,6 +31,7 @@ const createInternalClientMock = (): DeeplyMockedKeys => { '_events', '_eventsCount', '_maxListeners', + 'constructor', 'name', 'serializer', 'connectionPool', @@ -38,35 +39,57 @@ const createInternalClientMock = (): DeeplyMockedKeys => { 'helpers', ]; + const getAllPropertyDescriptors = (obj: Record) => { + const descriptors = Object.entries(Object.getOwnPropertyDescriptors(obj)); + let prototype = Object.getPrototypeOf(obj); + while (prototype != null && prototype !== Object.prototype) { + descriptors.push(...Object.entries(Object.getOwnPropertyDescriptors(prototype))); + prototype = Object.getPrototypeOf(prototype); + } + return descriptors; + }; + const mockify = (obj: Record, omitted: string[] = []) => { - Object.keys(obj) - .filter((key) => !omitted.includes(key)) - .forEach((key) => { - const propType = typeof obj[key]; - if (propType === 'function') { + // the @elastic/elasticsearch::Client uses prototypical inheritance + // so we have to crawl up the prototype chain and get all descriptors + // to find everything that we should be mocking + const descriptors = getAllPropertyDescriptors(obj); + descriptors + .filter(([key]) => !omitted.includes(key)) + .forEach(([key, descriptor]) => { + if (typeof descriptor.value === 'function') { obj[key] = jest.fn(() => createSuccessTransportRequestPromise({})); - } else if (propType === 'object' && obj[key] != null) { - mockify(obj[key]); + } else if (typeof obj[key] === 'object' && obj[key] != null) { + mockify(obj[key], omitted); } }); }; mockify(client, omittedProps); - // client got some read-only (getter) properties - // so we need to extend it to override the getter-only props. - const mock: any = { ...client }; + client.close = jest.fn().mockReturnValue(Promise.resolve()); + client.child = jest.fn().mockImplementation(() => createInternalClientMock()); + + const mockGetter = (obj: Record, propertyName: string) => { + Object.defineProperty(obj, propertyName, { + configurable: true, + enumerable: false, + get: () => jest.fn(), + set: undefined, + }); + }; - mock.transport = { + // `on`, `off`, and `once` are properties without a setter. + // We can't `client.on = jest.fn()` because the following error will be thrown: + // TypeError: Cannot set property on of # which has only a getter + mockGetter(client, 'on'); + mockGetter(client, 'off'); + mockGetter(client, 'once'); + client.transport = { request: jest.fn(), }; - mock.close = jest.fn().mockReturnValue(Promise.resolve()); - mock.child = jest.fn().mockImplementation(() => createInternalClientMock()); - mock.on = jest.fn(); - mock.off = jest.fn(); - mock.once = jest.fn(); - return (mock as unknown) as DeeplyMockedKeys; + return client as DeeplyMockedKeys; }; export type ElasticsearchClientMock = DeeplyMockedKeys; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index e136c699f7246..70ef93963c69f 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -339,14 +339,7 @@ export { SavedObjectsMigrationVersion, } from './types'; -export { - LegacyServiceSetupDeps, - LegacyServiceStartDeps, - LegacyServiceDiscoverPlugins, - LegacyConfig, - LegacyUiExports, - LegacyInternals, -} from './legacy'; +export { LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyConfig } from './legacy'; export { CoreStatus, diff --git a/src/core/server/legacy/config/ensure_valid_configuration.test.ts b/src/core/server/legacy/config/ensure_valid_configuration.test.ts index 702840b8a0a6a..700fe69954655 100644 --- a/src/core/server/legacy/config/ensure_valid_configuration.test.ts +++ b/src/core/server/legacy/config/ensure_valid_configuration.test.ts @@ -39,17 +39,12 @@ describe('ensureValidConfiguration', () => { configService as any, { settings: 'settings', - pluginSpecs: 'pluginSpecs', - disabledPluginSpecs: 'disabledPluginSpecs', - pluginExtendedConfig: 'pluginExtendedConfig', - uiExports: 'uiExports', + legacyConfig: 'pluginExtendedConfig', } as any ); expect(getUnusedConfigKeys).toHaveBeenCalledTimes(1); expect(getUnusedConfigKeys).toHaveBeenCalledWith({ coreHandledConfigPaths: ['core', 'elastic'], - pluginSpecs: 'pluginSpecs', - disabledPluginSpecs: 'disabledPluginSpecs', settings: 'settings', legacyConfig: 'pluginExtendedConfig', }); diff --git a/src/core/server/legacy/config/ensure_valid_configuration.ts b/src/core/server/legacy/config/ensure_valid_configuration.ts index 5cd1603ea65fb..34f98b9b3a795 100644 --- a/src/core/server/legacy/config/ensure_valid_configuration.ts +++ b/src/core/server/legacy/config/ensure_valid_configuration.ts @@ -19,19 +19,17 @@ import { getUnusedConfigKeys } from './get_unused_config_keys'; import { ConfigService } from '../../config'; -import { LegacyServiceDiscoverPlugins } from '../types'; import { CriticalError } from '../../errors'; +import { LegacyServiceSetupConfig } from '../types'; export async function ensureValidConfiguration( configService: ConfigService, - { pluginSpecs, disabledPluginSpecs, pluginExtendedConfig, settings }: LegacyServiceDiscoverPlugins + { legacyConfig, settings }: LegacyServiceSetupConfig ) { const unusedConfigKeys = await getUnusedConfigKeys({ coreHandledConfigPaths: await configService.getUsedPaths(), - pluginSpecs, - disabledPluginSpecs, settings, - legacyConfig: pluginExtendedConfig, + legacyConfig, }); if (unusedConfigKeys.length > 0) { diff --git a/src/core/server/legacy/config/get_unused_config_keys.test.ts b/src/core/server/legacy/config/get_unused_config_keys.test.ts index f8506b5744030..6ce69fca0270a 100644 --- a/src/core/server/legacy/config/get_unused_config_keys.test.ts +++ b/src/core/server/legacy/config/get_unused_config_keys.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { LegacyPluginSpec, LegacyConfig, LegacyVars } from '../types'; +import { LegacyConfig, LegacyVars } from '../types'; import { getUnusedConfigKeys } from './get_unused_config_keys'; describe('getUnusedConfigKeys', () => { @@ -35,8 +35,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: {}, legacyConfig: getConfig(), }) @@ -47,8 +45,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { presentInBoth: true, alsoInBoth: 'someValue', @@ -65,8 +61,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { presentInBoth: true, }, @@ -82,8 +76,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { presentInBoth: true, onlyInSetting: 'value', @@ -99,8 +91,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { elasticsearch: { username: 'foo', @@ -121,8 +111,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { env: 'development', }, @@ -139,8 +127,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { prop: ['a', 'b', 'c'], }, @@ -152,40 +138,10 @@ describe('getUnusedConfigKeys', () => { }); }); - it('ignores config for plugins that are disabled', async () => { - expect( - await getUnusedConfigKeys({ - coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [ - ({ - id: 'foo', - getConfigPrefix: () => 'foo.bar', - } as unknown) as LegacyPluginSpec, - ], - settings: { - foo: { - bar: { - unused: true, - }, - }, - plugin: { - missingProp: false, - }, - }, - legacyConfig: getConfig({ - prop: 'a', - }), - }) - ).toEqual(['plugin.missingProp']); - }); - it('ignores properties managed by the new platform', async () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: ['core', 'foo.bar'], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { core: { prop: 'value', @@ -204,8 +160,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: ['core', 'array'], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { core: { prop: 'value', diff --git a/src/core/server/legacy/config/get_unused_config_keys.ts b/src/core/server/legacy/config/get_unused_config_keys.ts index c15c3b270df05..5bbe169033e39 100644 --- a/src/core/server/legacy/config/get_unused_config_keys.ts +++ b/src/core/server/legacy/config/get_unused_config_keys.ts @@ -19,30 +19,20 @@ import { difference } from 'lodash'; import { getFlattenedObject } from '@kbn/std'; -import { unset } from '../../../../legacy/utils'; import { hasConfigPathIntersection } from '../../config'; -import { LegacyPluginSpec, LegacyConfig, LegacyVars } from '../types'; +import { LegacyConfig, LegacyVars } from '../types'; const getFlattenedKeys = (object: object) => Object.keys(getFlattenedObject(object)); export async function getUnusedConfigKeys({ coreHandledConfigPaths, - pluginSpecs, - disabledPluginSpecs, settings, legacyConfig, }: { coreHandledConfigPaths: string[]; - pluginSpecs: LegacyPluginSpec[]; - disabledPluginSpecs: LegacyPluginSpec[]; settings: LegacyVars; legacyConfig: LegacyConfig; }) { - // remove config values from disabled plugins - for (const spec of disabledPluginSpecs) { - unset(settings, spec.getConfigPrefix()); - } - const inputKeys = getFlattenedKeys(settings); const appliedKeys = getFlattenedKeys(legacyConfig.get()); diff --git a/src/core/server/legacy/index.ts b/src/core/server/legacy/index.ts index 6b0963e3129c6..1a0bc8955be0f 100644 --- a/src/core/server/legacy/index.ts +++ b/src/core/server/legacy/index.ts @@ -20,8 +20,6 @@ /** @internal */ export { ensureValidConfiguration } from './config'; /** @internal */ -export { LegacyInternals } from './legacy_internals'; -/** @internal */ export { LegacyService, ILegacyService } from './legacy_service'; /** @internal */ export * from './types'; diff --git a/src/core/server/legacy/legacy_internals.test.ts b/src/core/server/legacy/legacy_internals.test.ts deleted file mode 100644 index 935e36a989a0c..0000000000000 --- a/src/core/server/legacy/legacy_internals.test.ts +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Server } from 'hapi'; - -import { configMock } from '../config/mocks'; -import { httpServiceMock } from '../http/http_service.mock'; -import { httpServerMock } from '../http/http_server.mocks'; -import { findLegacyPluginSpecsMock } from './legacy_service.test.mocks'; -import { LegacyInternals } from './legacy_internals'; -import { ILegacyInternals, LegacyConfig, LegacyVars, LegacyUiExports } from './types'; - -function varsProvider(vars: LegacyVars, configValue?: any) { - return { - fn: jest.fn().mockReturnValue(vars), - pluginSpec: { - readConfigValue: jest.fn().mockReturnValue(configValue), - }, - }; -} - -describe('LegacyInternals', () => { - describe('getInjectedUiAppVars()', () => { - let uiExports: LegacyUiExports; - let config: LegacyConfig; - let server: Server; - let legacyInternals: ILegacyInternals; - - beforeEach(async () => { - uiExports = findLegacyPluginSpecsMock().uiExports; - config = configMock.create() as any; - server = httpServiceMock.createInternalSetupContract().server; - legacyInternals = new LegacyInternals(uiExports, config, server); - }); - - it('gets with no injectors', async () => { - await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot( - `Object {}` - ); - }); - - it('gets with no matching injectors', async () => { - const injector = jest.fn().mockResolvedValue({ not: 'core' }); - legacyInternals.injectUiAppVars('not-core', injector); - - await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot( - `Object {}` - ); - expect(injector).not.toHaveBeenCalled(); - }); - - it('gets with single matching injector', async () => { - const injector = jest.fn().mockResolvedValue({ is: 'core' }); - legacyInternals.injectUiAppVars('core', injector); - - await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(` - Object { - "is": "core", - } - `); - expect(injector).toHaveBeenCalled(); - }); - - it('gets with multiple matching injectors', async () => { - const injectors = [ - jest.fn().mockResolvedValue({ is: 'core' }), - jest.fn().mockReturnValue({ sync: 'injector' }), - jest.fn().mockResolvedValue({ is: 'merged-core' }), - ]; - - injectors.forEach((injector) => legacyInternals.injectUiAppVars('core', injector)); - - await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(` - Object { - "is": "merged-core", - "sync": "injector", - } - `); - expect(injectors[0]).toHaveBeenCalled(); - expect(injectors[1]).toHaveBeenCalled(); - expect(injectors[2]).toHaveBeenCalled(); - }); - }); - - describe('getVars()', () => { - let uiExports: LegacyUiExports; - let config: LegacyConfig; - let server: Server; - let legacyInternals: LegacyInternals; - - beforeEach(async () => { - uiExports = findLegacyPluginSpecsMock().uiExports; - config = configMock.create() as any; - server = httpServiceMock.createInternalSetupContract().server; - legacyInternals = new LegacyInternals(uiExports, config, server); - }); - - it('gets: no default injectors, no injected vars replacers, no ui app injectors, no inject arg', async () => { - const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest()); - - expect(vars).toMatchInlineSnapshot(`Object {}`); - }); - - it('gets: with default injectors, no injected vars replacers, no ui app injectors, no inject arg', async () => { - uiExports.defaultInjectedVarProviders = [ - varsProvider({ alpha: 'alpha' }), - varsProvider({ gamma: 'gamma' }), - varsProvider({ alpha: 'beta' }), - ]; - - const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest()); - - expect(vars).toMatchInlineSnapshot(` - Object { - "alpha": "beta", - "gamma": "gamma", - } - `); - }); - - it('gets: no default injectors, with injected vars replacers, with ui app injectors, no inject arg', async () => { - uiExports.injectedVarsReplacers = [ - jest.fn(async (vars) => ({ ...vars, added: 'key' })), - jest.fn((vars) => vars), - jest.fn((vars) => ({ replaced: 'all' })), - jest.fn(async (vars) => ({ ...vars, added: 'last-key' })), - ]; - - const request = httpServerMock.createRawRequest(); - const vars = await legacyInternals.getVars('core', request); - - expect(vars).toMatchInlineSnapshot(` - Object { - "added": "last-key", - "replaced": "all", - } - `); - }); - - it('gets: no default injectors, no injected vars replacers, with ui app injectors, no inject arg', async () => { - legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' })); - legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' })); - legacyInternals.injectUiAppVars('core', async () => ({ is: 'merged-core' })); - - const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest()); - - expect(vars).toMatchInlineSnapshot(` - Object { - "is": "merged-core", - "sync": "injector", - } - `); - }); - - it('gets: no default injectors, no injected vars replacers, no ui app injectors, with inject arg', async () => { - const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest(), { - injected: 'arg', - }); - - expect(vars).toMatchInlineSnapshot(` - Object { - "injected": "arg", - } - `); - }); - - it('gets: with default injectors, with injected vars replacers, with ui app injectors, with inject arg', async () => { - uiExports.defaultInjectedVarProviders = [ - varsProvider({ alpha: 'alpha' }), - varsProvider({ gamma: 'gamma' }), - varsProvider({ alpha: 'beta' }), - ]; - uiExports.injectedVarsReplacers = [jest.fn(async (vars) => ({ ...vars, gamma: 'delta' }))]; - - legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' })); - legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' })); - legacyInternals.injectUiAppVars('core', async () => ({ is: 'merged-core' })); - - const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest(), { - injected: 'arg', - sync: 'arg', - }); - - expect(vars).toMatchInlineSnapshot(` - Object { - "alpha": "beta", - "gamma": "delta", - "injected": "arg", - "is": "merged-core", - "sync": "arg", - } - `); - }); - }); -}); diff --git a/src/core/server/legacy/legacy_internals.ts b/src/core/server/legacy/legacy_internals.ts deleted file mode 100644 index 628ca4ed12f6b..0000000000000 --- a/src/core/server/legacy/legacy_internals.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Server } from 'hapi'; - -import { KibanaRequest, LegacyRequest } from '../http'; -import { ensureRawRequest } from '../http/router'; -import { mergeVars } from './merge_vars'; -import { ILegacyInternals, LegacyVars, VarsInjector, LegacyConfig, LegacyUiExports } from './types'; - -/** - * @internal - * @deprecated - */ -export class LegacyInternals implements ILegacyInternals { - private readonly injectors = new Map>(); - private cachedDefaultVars?: LegacyVars; - - constructor( - private readonly uiExports: LegacyUiExports, - private readonly config: LegacyConfig, - private readonly server: Server - ) {} - - private get defaultVars(): LegacyVars { - if (this.cachedDefaultVars) { - return this.cachedDefaultVars; - } - - const { defaultInjectedVarProviders = [] } = this.uiExports; - - return (this.cachedDefaultVars = defaultInjectedVarProviders.reduce( - (vars, { fn, pluginSpec }) => - mergeVars(vars, fn(this.server, pluginSpec.readConfigValue(this.config, []))), - {} - )); - } - - private replaceVars(vars: LegacyVars, request: KibanaRequest | LegacyRequest) { - const { injectedVarsReplacers = [] } = this.uiExports; - - return injectedVarsReplacers.reduce( - async (injected, replacer) => - replacer(await injected, ensureRawRequest(request), this.server), - Promise.resolve(vars) - ); - } - - public injectUiAppVars(id: string, injector: VarsInjector) { - if (!this.injectors.has(id)) { - this.injectors.set(id, new Set()); - } - - this.injectors.get(id)!.add(injector); - } - - public getInjectedUiAppVars(id: string) { - return [...(this.injectors.get(id) || [])].reduce( - async (promise, injector) => ({ - ...(await promise), - ...(await injector()), - }), - Promise.resolve({}) - ); - } - - public async getVars( - id: string, - request: KibanaRequest | LegacyRequest, - injected: LegacyVars = {} - ) { - return this.replaceVars( - mergeVars(this.defaultVars, await this.getInjectedUiAppVars(id), injected), - request - ); - } -} diff --git a/src/core/server/legacy/legacy_service.mock.ts b/src/core/server/legacy/legacy_service.mock.ts index ab501bd6bb53b..781874f702cf8 100644 --- a/src/core/server/legacy/legacy_service.mock.ts +++ b/src/core/server/legacy/legacy_service.mock.ts @@ -18,26 +18,13 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; import { LegacyService } from './legacy_service'; -import { LegacyConfig, LegacyServiceDiscoverPlugins, LegacyServiceSetupDeps } from './types'; +import { LegacyConfig, LegacyServiceSetupDeps } from './types'; type LegacyServiceMock = jest.Mocked & { legacyId: symbol }>; -const createDiscoverPluginsMock = (): LegacyServiceDiscoverPlugins => ({ - pluginSpecs: [], - uiExports: {}, - navLinks: [], - pluginExtendedConfig: { - get: jest.fn(), - has: jest.fn(), - set: jest.fn(), - }, - disabledPluginSpecs: [], - settings: {}, -}); - const createLegacyServiceMock = (): LegacyServiceMock => ({ legacyId: Symbol(), - discoverPlugins: jest.fn().mockResolvedValue(createDiscoverPluginsMock()), + setupLegacyConfig: jest.fn(), setup: jest.fn(), start: jest.fn(), stop: jest.fn(), @@ -52,6 +39,5 @@ const createLegacyConfigMock = (): jest.Mocked => ({ export const legacyServiceMock = { create: createLegacyServiceMock, createSetupContract: (deps: LegacyServiceSetupDeps) => createLegacyServiceMock().setup(deps), - createDiscoverPlugins: createDiscoverPluginsMock, createLegacyConfig: createLegacyConfigMock, }; diff --git a/src/core/server/legacy/legacy_service.test.mocks.ts b/src/core/server/legacy/legacy_service.test.mocks.ts deleted file mode 100644 index 9ad554d63add0..0000000000000 --- a/src/core/server/legacy/legacy_service.test.mocks.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { LegacyVars } from './types'; - -export const findLegacyPluginSpecsMock = jest.fn().mockImplementation((settings: LegacyVars) => ({ - pluginSpecs: [], - pluginExtendedConfig: { - has: jest.fn(), - get: jest.fn().mockReturnValue(settings), - set: jest.fn(), - }, - disabledPluginSpecs: [], - uiExports: {}, - navLinks: [], -})); -jest.doMock('./plugins/find_legacy_plugin_specs', () => ({ - findLegacyPluginSpecs: findLegacyPluginSpecsMock, -})); - -export const logLegacyThirdPartyPluginDeprecationWarningMock = jest.fn(); -jest.doMock('./plugins/log_legacy_plugins_warning', () => ({ - logLegacyThirdPartyPluginDeprecationWarning: logLegacyThirdPartyPluginDeprecationWarningMock, -})); diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index a6fe95deb3979..57009f0d35c16 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -19,10 +19,6 @@ jest.mock('../../../legacy/server/kbn_server'); jest.mock('./cluster_manager'); -import { - findLegacyPluginSpecsMock, - logLegacyThirdPartyPluginDeprecationWarningMock, -} from './legacy_service.test.mocks'; import { BehaviorSubject, throwError } from 'rxjs'; import { REPO_ROOT } from '@kbn/dev-utils'; @@ -44,8 +40,7 @@ import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mo import { httpResourcesMock } from '../http_resources/http_resources_service.mock'; import { setupMock as renderingServiceMock } from '../rendering/__mocks__/rendering_service'; import { environmentServiceMock } from '../environment/environment_service.mock'; -import { findLegacyPluginSpecs } from './plugins'; -import { LegacyVars, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types'; +import { LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types'; import { LegacyService } from './legacy_service'; import { coreMock } from '../mocks'; import { statusServiceMock } from '../status/status_service.mock'; @@ -73,7 +68,6 @@ beforeEach(() => { configService = configServiceMock.create(); environmentSetup = environmentServiceMock.createSetupContract(); - findLegacyPluginSpecsMock.mockClear(); MockKbnServer.prototype.ready = jest.fn().mockReturnValue(Promise.resolve()); MockKbnServer.prototype.listen = jest.fn(); @@ -149,10 +143,10 @@ describe('once LegacyService is set up with connection info', () => { coreId, env, logger, - configService: configService as any, + configService, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -160,13 +154,14 @@ describe('once LegacyService is set up with connection info', () => { expect(MockKbnServer).toHaveBeenCalledWith( { path: { autoListen: true }, server: { autoListen: true } }, // Because of the mock, path also gets the value expect.objectContaining({ get: expect.any(Function) }), - expect.any(Object), - { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] } + expect.any(Object) + ); + expect(MockKbnServer.mock.calls[0][1].get()).toEqual( + expect.objectContaining({ + path: expect.objectContaining({ autoListen: true }), + server: expect.objectContaining({ autoListen: true }), + }) ); - expect(MockKbnServer.mock.calls[0][1].get()).toEqual({ - path: { autoListen: true }, - server: { autoListen: true }, - }); const [mockKbnServer] = MockKbnServer.mock.instances; expect(mockKbnServer.listen).toHaveBeenCalledTimes(1); @@ -182,7 +177,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -190,13 +185,12 @@ describe('once LegacyService is set up with connection info', () => { expect(MockKbnServer).toHaveBeenCalledWith( { path: { autoListen: false }, server: { autoListen: true } }, expect.objectContaining({ get: expect.any(Function) }), - expect.any(Object), - { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] } + expect.any(Object) ); - expect(MockKbnServer.mock.calls[0][1].get()).toEqual({ - path: { autoListen: false }, - server: { autoListen: true }, - }); + + const legacyConfig = MockKbnServer.mock.calls[0][1].get(); + expect(legacyConfig.path.autoListen).toBe(false); + expect(legacyConfig.server.autoListen).toBe(true); const [mockKbnServer] = MockKbnServer.mock.instances; expect(mockKbnServer.ready).toHaveBeenCalledTimes(1); @@ -214,7 +208,7 @@ describe('once LegacyService is set up with connection info', () => { configService: configService as any, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot( `"something failed"` @@ -234,11 +228,11 @@ describe('once LegacyService is set up with connection info', () => { configService: configService as any, }); - await expect(legacyService.discoverPlugins()).rejects.toThrowErrorMatchingInlineSnapshot( + await expect(legacyService.setupLegacyConfig()).rejects.toThrowErrorMatchingInlineSnapshot( `"something failed"` ); await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingInlineSnapshot( - `"Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()"` + `"Legacy config not initialized yet. Ensure LegacyService.setupLegacyConfig() is called before LegacyService.setup()"` ); await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot( `"Legacy service is not setup yet."` @@ -255,7 +249,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -276,7 +270,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -301,7 +295,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -321,7 +315,7 @@ describe('once LegacyService is set up without connection info', () => { let legacyService: LegacyService; beforeEach(async () => { legacyService = new LegacyService({ coreId, env, logger, configService: configService as any }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); }); @@ -331,13 +325,13 @@ describe('once LegacyService is set up without connection info', () => { expect(MockKbnServer).toHaveBeenCalledWith( { path: {}, server: { autoListen: true } }, expect.objectContaining({ get: expect.any(Function) }), - expect.any(Object), - { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] } + expect.any(Object) + ); + expect(MockKbnServer.mock.calls[0][1].get()).toEqual( + expect.objectContaining({ + server: expect.objectContaining({ autoListen: true }), + }) ); - expect(MockKbnServer.mock.calls[0][1].get()).toEqual({ - path: {}, - server: { autoListen: true }, - }); }); test('reconfigures logging configuration if new config is received.', async () => { @@ -375,7 +369,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { configService: configService as any, }); - await devClusterLegacyService.discoverPlugins(); + await devClusterLegacyService.setupLegacyConfig(); await devClusterLegacyService.setup(setupDeps); await devClusterLegacyService.start(startDeps); @@ -404,7 +398,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { configService: configService as any, }); - await devClusterLegacyService.discoverPlugins(); + await devClusterLegacyService.setupLegacyConfig(); await devClusterLegacyService.setup(setupDeps); await devClusterLegacyService.start(startDeps); @@ -434,50 +428,6 @@ describe('start', () => { }); }); -describe('#discoverPlugins()', () => { - it('calls findLegacyPluginSpecs with correct parameters', async () => { - const legacyService = new LegacyService({ - coreId, - env, - logger, - configService: configService as any, - }); - - await legacyService.discoverPlugins(); - expect(findLegacyPluginSpecs).toHaveBeenCalledTimes(1); - expect(findLegacyPluginSpecs).toHaveBeenCalledWith(expect.any(Object), logger, env.packageInfo); - }); - - it(`logs deprecations for legacy third party plugins`, async () => { - const pluginSpecs = [{ getId: () => 'pluginA' }, { getId: () => 'pluginB' }]; - findLegacyPluginSpecsMock.mockImplementation( - (settings) => - Promise.resolve({ - pluginSpecs, - pluginExtendedConfig: settings, - disabledPluginSpecs: [], - uiExports: {}, - navLinks: [], - }) as any - ); - - const legacyService = new LegacyService({ - coreId, - env, - logger, - configService: configService as any, - }); - - await legacyService.discoverPlugins(); - - expect(logLegacyThirdPartyPluginDeprecationWarningMock).toHaveBeenCalledTimes(1); - expect(logLegacyThirdPartyPluginDeprecationWarningMock).toHaveBeenCalledWith({ - specs: pluginSpecs, - log: expect.any(Object), - }); - }); -}); - test('Sets the server.uuid property on the legacy configuration', async () => { configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true })); const legacyService = new LegacyService({ @@ -489,23 +439,8 @@ test('Sets the server.uuid property on the legacy configuration', async () => { environmentSetup.instanceUuid = 'UUID_FROM_SERVICE'; - const configSetMock = jest.fn(); - - findLegacyPluginSpecsMock.mockImplementation((settings: LegacyVars) => ({ - pluginSpecs: [], - pluginExtendedConfig: { - has: jest.fn(), - get: jest.fn().mockReturnValue(settings), - set: configSetMock, - }, - disabledPluginSpecs: [], - uiExports: {}, - navLinks: [], - })); - - await legacyService.discoverPlugins(); + const { legacyConfig } = await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); - expect(configSetMock).toHaveBeenCalledTimes(1); - expect(configSetMock).toHaveBeenCalledWith('server.uuid', 'UUID_FROM_SERVICE'); + expect(legacyConfig.get('server.uuid')).toBe('UUID_FROM_SERVICE'); }); diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 4dc22be2a9971..086e20c98c1a3 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -16,11 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import type { PublicMethodsOf } from '@kbn/utility-types'; + import { combineLatest, ConnectableObservable, EMPTY, Observable, Subscription } from 'rxjs'; import { first, map, publishReplay, tap } from 'rxjs/operators'; - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { PathConfigType } from '@kbn/utils'; + +// @ts-expect-error legacy config class +import { Config as LegacyConfigClass } from '../../../legacy/server/config'; import { CoreService } from '../../types'; import { Config } from '../config'; import { CoreContext } from '../core_context'; @@ -28,17 +31,7 @@ import { CspConfigType, config as cspConfig } from '../csp'; import { DevConfig, DevConfigType, config as devConfig } from '../dev'; import { BasePathProxyServer, HttpConfig, HttpConfigType, config as httpConfig } from '../http'; import { Logger } from '../logging'; -import { findLegacyPluginSpecs, logLegacyThirdPartyPluginDeprecationWarning } from './plugins'; -import { - ILegacyInternals, - LegacyServiceSetupDeps, - LegacyServiceStartDeps, - LegacyPlugins, - LegacyServiceDiscoverPlugins, - LegacyConfig, - LegacyVars, -} from './types'; -import { LegacyInternals } from './legacy_internals'; +import { LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyConfig, LegacyVars } from './types'; import { CoreSetup, CoreStart } from '..'; interface LegacyKbnServer { @@ -80,9 +73,7 @@ export class LegacyService implements CoreService { private setupDeps?: LegacyServiceSetupDeps; private update$?: ConnectableObservable<[Config, PathConfigType]>; private legacyRawConfig?: LegacyConfig; - private legacyPlugins?: LegacyPlugins; private settings?: LegacyVars; - public legacyInternals?: ILegacyInternals; constructor(private readonly coreContext: CoreContext) { const { logger, configService } = coreContext; @@ -97,11 +88,11 @@ export class LegacyService implements CoreService { ).pipe(map(([http, csp]) => new HttpConfig(http, csp))); } - public async discoverPlugins(): Promise { - this.update$ = combineLatest( + public async setupLegacyConfig() { + this.update$ = combineLatest([ this.coreContext.configService.getConfig$(), - this.coreContext.configService.atPath('path') - ).pipe( + this.coreContext.configService.atPath('path'), + ]).pipe( tap(([config, pathConfig]) => { if (this.kbnServer !== undefined) { this.kbnServer.applyLoggingConfiguration(getLegacyRawConfig(config, pathConfig)); @@ -120,74 +111,33 @@ export class LegacyService implements CoreService { ) .toPromise(); - const { - pluginSpecs, - pluginExtendedConfig, - disabledPluginSpecs, - uiExports, - navLinks, - } = await findLegacyPluginSpecs( - this.settings, - this.coreContext.logger, - this.coreContext.env.packageInfo - ); - - logLegacyThirdPartyPluginDeprecationWarning({ - specs: pluginSpecs, - log: this.log, - }); - - this.legacyPlugins = { - pluginSpecs, - disabledPluginSpecs, - uiExports, - navLinks, - }; - - this.legacyRawConfig = pluginExtendedConfig; - - // check for unknown uiExport types - if (uiExports.unknown && uiExports.unknown.length > 0) { - throw new Error( - `Unknown uiExport types: ${uiExports.unknown - .map(({ pluginSpec, type }) => `${type} from ${pluginSpec.getId()}`) - .join(', ')}` - ); - } + this.legacyRawConfig = LegacyConfigClass.withDefaultSchema(this.settings); return { - pluginSpecs, - disabledPluginSpecs, - uiExports, - navLinks, - pluginExtendedConfig, settings: this.settings, + legacyConfig: this.legacyRawConfig!, }; } public async setup(setupDeps: LegacyServiceSetupDeps) { this.log.debug('setting up legacy service'); - if (!this.legacyPlugins) { + if (!this.legacyRawConfig) { throw new Error( - 'Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()' + 'Legacy config not initialized yet. Ensure LegacyService.setupLegacyConfig() is called before LegacyService.setup()' ); } // propagate the instance uuid to the legacy config, as it was the legacy way to access it. this.legacyRawConfig!.set('server.uuid', setupDeps.core.environment.instanceUuid); + this.setupDeps = setupDeps; - this.legacyInternals = new LegacyInternals( - this.legacyPlugins.uiExports, - this.legacyRawConfig!, - setupDeps.core.http.server - ); } public async start(startDeps: LegacyServiceStartDeps) { const { setupDeps } = this; - if (!setupDeps || !this.legacyPlugins) { + if (!setupDeps || !this.legacyRawConfig) { throw new Error('Legacy service is not setup yet.'); } @@ -201,8 +151,7 @@ export class LegacyService implements CoreService { this.settings!, this.legacyRawConfig!, setupDeps, - startDeps, - this.legacyPlugins! + startDeps ); } } @@ -245,8 +194,7 @@ export class LegacyService implements CoreService { settings: LegacyVars, config: LegacyConfig, setupDeps: LegacyServiceSetupDeps, - startDeps: LegacyServiceStartDeps, - legacyPlugins: LegacyPlugins + startDeps: LegacyServiceStartDeps ) { const coreStart: CoreStart = { capabilities: startDeps.core.capabilities, @@ -337,36 +285,26 @@ export class LegacyService implements CoreService { // eslint-disable-next-line @typescript-eslint/no-var-requires const KbnServer = require('../../../legacy/server/kbn_server'); - const kbnServer: LegacyKbnServer = new KbnServer( - settings, - config, - { - env: { - mode: this.coreContext.env.mode, - packageInfo: this.coreContext.env.packageInfo, - }, - setupDeps: { - core: coreSetup, - plugins: setupDeps.plugins, - }, - startDeps: { - core: coreStart, - plugins: startDeps.plugins, - }, - __internals: { - http: { - registerStaticDir: setupDeps.core.http.registerStaticDir, - }, - hapiServer: setupDeps.core.http.server, - uiPlugins: setupDeps.uiPlugins, - elasticsearch: setupDeps.core.elasticsearch, - rendering: setupDeps.core.rendering, - legacy: this.legacyInternals, - }, - logger: this.coreContext.logger, + const kbnServer: LegacyKbnServer = new KbnServer(settings, config, { + env: { + mode: this.coreContext.env.mode, + packageInfo: this.coreContext.env.packageInfo, }, - legacyPlugins - ); + setupDeps: { + core: coreSetup, + plugins: setupDeps.plugins, + }, + startDeps: { + core: coreStart, + plugins: startDeps.plugins, + }, + __internals: { + hapiServer: setupDeps.core.http.server, + uiPlugins: setupDeps.uiPlugins, + rendering: setupDeps.core.rendering, + }, + logger: this.coreContext.logger, + }); // The kbnWorkerType check is necessary to prevent the repl // from being started multiple times in different processes. diff --git a/src/core/server/legacy/plugins/collect_ui_exports.js b/src/core/server/legacy/plugins/collect_ui_exports.js deleted file mode 100644 index 842ab554d79d1..0000000000000 --- a/src/core/server/legacy/plugins/collect_ui_exports.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { collectUiExports } from '../../../../legacy/ui/ui_exports/collect_ui_exports'; diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts deleted file mode 100644 index cb4277b130a88..0000000000000 --- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Observable, merge, forkJoin } from 'rxjs'; -import { toArray, tap, distinct, map } from 'rxjs/operators'; - -import { - findPluginSpecs, - defaultConfig, - // @ts-expect-error -} from '../../../../legacy/plugin_discovery/find_plugin_specs.js'; -// @ts-expect-error -import { collectUiExports as collectLegacyUiExports } from './collect_ui_exports'; - -import { LoggerFactory } from '../../logging'; -import { PackageInfo } from '../../config'; -import { LegacyUiExports, LegacyPluginSpec, LegacyPluginPack, LegacyConfig } from '../types'; - -export async function findLegacyPluginSpecs( - settings: unknown, - loggerFactory: LoggerFactory, - packageInfo: PackageInfo -) { - const configToMutate: LegacyConfig = defaultConfig(settings); - const { - pack$, - invalidDirectoryError$, - invalidPackError$, - otherError$, - deprecation$, - invalidVersionSpec$, - spec$, - disabledSpec$, - }: { - pack$: Observable; - invalidDirectoryError$: Observable<{ path: string }>; - invalidPackError$: Observable<{ path: string }>; - otherError$: Observable; - deprecation$: Observable<{ spec: LegacyPluginSpec; message: string }>; - invalidVersionSpec$: Observable; - spec$: Observable; - disabledSpec$: Observable; - } = findPluginSpecs(settings, configToMutate) as any; - - const logger = loggerFactory.get('legacy-plugins'); - - const log$ = merge( - pack$.pipe( - tap((definition) => { - const path = definition.getPath(); - logger.debug(`Found plugin at ${path}`, { path }); - }) - ), - - invalidDirectoryError$.pipe( - tap((error) => { - logger.warn(`Unable to scan directory for plugins "${error.path}"`, { - err: error, - dir: error.path, - }); - }) - ), - - invalidPackError$.pipe( - tap((error) => { - logger.warn(`Skipping non-plugin directory at ${error.path}`, { - path: error.path, - }); - }) - ), - - otherError$.pipe( - tap((error) => { - // rethrow unhandled errors, which will fail the server - throw error; - }) - ), - - invalidVersionSpec$.pipe( - map((spec) => { - const name = spec.getId(); - const pluginVersion = spec.getExpectedKibanaVersion(); - const kibanaVersion = packageInfo.version; - return `Plugin "${name}" was disabled because it expected Kibana version "${pluginVersion}", and found "${kibanaVersion}".`; - }), - distinct(), - tap((message) => { - logger.warn(message); - }) - ), - - deprecation$.pipe( - tap(({ spec, message }) => { - const deprecationLogger = loggerFactory.get( - 'plugins', - spec.getConfigPrefix(), - 'config', - 'deprecation' - ); - deprecationLogger.warn(message); - }) - ) - ); - - const [disabledPluginSpecs, pluginSpecs] = await forkJoin( - disabledSpec$.pipe(toArray()), - spec$.pipe(toArray()), - log$.pipe(toArray()) - ).toPromise(); - const uiExports: LegacyUiExports = collectLegacyUiExports(pluginSpecs); - - return { - disabledPluginSpecs, - pluginSpecs, - pluginExtendedConfig: configToMutate, - uiExports, - navLinks: [], - }; -} diff --git a/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts b/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts deleted file mode 100644 index 2317f1036ce42..0000000000000 --- a/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { loggerMock } from '../../logging/logger.mock'; -import { logLegacyThirdPartyPluginDeprecationWarning } from './log_legacy_plugins_warning'; -import { LegacyPluginSpec } from '../types'; - -const createPluginSpec = ({ id, path }: { id: string; path: string }): LegacyPluginSpec => { - return { - getId: () => id, - getExpectedKibanaVersion: () => 'kibana', - getConfigPrefix: () => 'plugin.config', - getPack: () => ({ - getPath: () => path, - }), - }; -}; - -describe('logLegacyThirdPartyPluginDeprecationWarning', () => { - let log: ReturnType; - - beforeEach(() => { - log = loggerMock.create(); - }); - - it('logs warning for third party plugins', () => { - logLegacyThirdPartyPluginDeprecationWarning({ - specs: [createPluginSpec({ id: 'plugin', path: '/some-external-path' })], - log, - }); - expect(log.warn).toHaveBeenCalledTimes(1); - expect(log.warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Some installed third party plugin(s) [plugin] are using the legacy plugin format and will no longer work in a future Kibana release. Please refer to https://ela.st/kibana-breaking-changes-8-0 for a list of breaking changes and https://ela.st/kibana-platform-migration for documentation on how to migrate legacy plugins.", - ] - `); - }); - - it('lists all the deprecated plugins and only log once', () => { - logLegacyThirdPartyPluginDeprecationWarning({ - specs: [ - createPluginSpec({ id: 'pluginA', path: '/abs/path/to/pluginA' }), - createPluginSpec({ id: 'pluginB', path: '/abs/path/to/pluginB' }), - createPluginSpec({ id: 'pluginC', path: '/abs/path/to/pluginC' }), - ], - log, - }); - expect(log.warn).toHaveBeenCalledTimes(1); - expect(log.warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Some installed third party plugin(s) [pluginA, pluginB, pluginC] are using the legacy plugin format and will no longer work in a future Kibana release. Please refer to https://ela.st/kibana-breaking-changes-8-0 for a list of breaking changes and https://ela.st/kibana-platform-migration for documentation on how to migrate legacy plugins.", - ] - `); - }); - - it('does not log warning for internal legacy plugins', () => { - logLegacyThirdPartyPluginDeprecationWarning({ - specs: [ - createPluginSpec({ - id: 'plugin', - path: '/absolute/path/to/kibana/src/legacy/core_plugins', - }), - createPluginSpec({ - id: 'plugin', - path: '/absolute/path/to/kibana/x-pack', - }), - ], - log, - }); - - expect(log.warn).not.toHaveBeenCalled(); - }); -}); diff --git a/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts b/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts deleted file mode 100644 index 4a4a1b1b0e60b..0000000000000 --- a/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Logger } from '../../logging'; -import { LegacyPluginSpec } from '../types'; - -const internalPaths = ['/src/legacy/core_plugins', '/x-pack']; - -// Use shortened URLs so destinations can be updated if/when documentation moves -// All platform team members have access to edit these -const breakingChangesUrl = 'https://ela.st/kibana-breaking-changes-8-0'; -const migrationGuideUrl = 'https://ela.st/kibana-platform-migration'; - -export const logLegacyThirdPartyPluginDeprecationWarning = ({ - specs, - log, -}: { - specs: LegacyPluginSpec[]; - log: Logger; -}) => { - const thirdPartySpecs = specs.filter(isThirdPartyPluginSpec); - if (thirdPartySpecs.length > 0) { - const pluginIds = thirdPartySpecs.map((spec) => spec.getId()); - log.warn( - `Some installed third party plugin(s) [${pluginIds.join( - ', ' - )}] are using the legacy plugin format and will no longer work in a future Kibana release. ` + - `Please refer to ${breakingChangesUrl} for a list of breaking changes ` + - `and ${migrationGuideUrl} for documentation on how to migrate legacy plugins.` - ); - } -}; - -const isThirdPartyPluginSpec = (spec: LegacyPluginSpec): boolean => { - const pluginPath = spec.getPack().getPath(); - return !internalPaths.some((internalPath) => pluginPath.indexOf(internalPath) > -1); -}; diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts index 1105308fd44cf..12bfddfff1961 100644 --- a/src/core/server/legacy/types.ts +++ b/src/core/server/legacy/types.ts @@ -17,10 +17,6 @@ * under the License. */ -import { Server } from 'hapi'; - -import { ChromeNavLink } from '../../public'; -import { KibanaRequest, LegacyRequest } from '../http'; import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { PluginsServiceSetup, PluginsServiceStart, UiPlugins } from '../plugins'; import { InternalRenderingServiceSetup } from '../rendering'; @@ -50,91 +46,6 @@ export interface LegacyConfig { set(config: LegacyVars): void; } -/** - * @internal - * @deprecated - */ -export interface LegacyPluginPack { - getPath(): string; -} - -/** - * @internal - * @deprecated - */ -export interface LegacyPluginSpec { - getId: () => unknown; - getExpectedKibanaVersion: () => string; - getConfigPrefix: () => string; - getPack: () => LegacyPluginPack; -} - -/** - * @internal - * @deprecated - */ -export interface VarsProvider { - fn: (server: Server, configValue: any) => LegacyVars; - pluginSpec: { - readConfigValue(config: any, key: string | string[]): any; - }; -} - -/** - * @internal - * @deprecated - */ -export type VarsInjector = () => LegacyVars; - -/** - * @internal - * @deprecated - */ -export type VarsReplacer = ( - vars: LegacyVars, - request: LegacyRequest, - server: Server -) => LegacyVars | Promise; - -/** - * @internal - * @deprecated - */ -export type LegacyNavLinkSpec = Partial & { - id: string; - title: string; - url: string; -}; - -/** - * @internal - * @deprecated - */ -export type LegacyAppSpec = Partial & { - pluginId?: string; - listed?: boolean; -}; - -/** - * @internal - * @deprecated - */ -export type LegacyNavLink = Omit & { - order: number; -}; - -/** - * @internal - * @deprecated - */ -export interface LegacyUiExports { - defaultInjectedVarProviders?: VarsProvider[]; - injectedVarsReplacers?: VarsReplacer[]; - navLinkSpecs?: LegacyNavLinkSpec[] | null; - uiAppSpecs?: Array; - unknown?: [{ pluginSpec: LegacyPluginSpec; type: unknown }]; -} - /** * @public * @deprecated @@ -158,43 +69,7 @@ export interface LegacyServiceStartDeps { * @internal * @deprecated */ -export interface ILegacyInternals { - /** - * Inject UI app vars for a particular plugin - */ - injectUiAppVars(id: string, injector: VarsInjector): void; - - /** - * Get all the merged injected UI app vars for a particular plugin - */ - getInjectedUiAppVars(id: string): Promise; - - /** - * Get the metadata vars for a particular plugin - */ - getVars( - id: string, - request: KibanaRequest | LegacyRequest, - injected?: LegacyVars - ): Promise; -} - -/** - * @internal - * @deprecated - */ -export interface LegacyPlugins { - disabledPluginSpecs: LegacyPluginSpec[]; - pluginSpecs: LegacyPluginSpec[]; - uiExports: LegacyUiExports; - navLinks: LegacyNavLink[]; -} - -/** - * @internal - * @deprecated - */ -export interface LegacyServiceDiscoverPlugins extends LegacyPlugins { - pluginExtendedConfig: LegacyConfig; +export interface LegacyServiceSetupConfig { + legacyConfig: LegacyConfig; settings: LegacyVars; } diff --git a/src/core/server/metrics/collectors/cgroup.test.ts b/src/core/server/metrics/collectors/cgroup.test.ts index 39f917b9f0ba1..163646bf55424 100644 --- a/src/core/server/metrics/collectors/cgroup.test.ts +++ b/src/core/server/metrics/collectors/cgroup.test.ts @@ -18,6 +18,7 @@ */ import mockFs from 'mock-fs'; +import { loggerMock } from '@kbn/logging/target/mocks'; import { OsCgroupMetricsCollector } from './cgroup'; describe('OsCgroupMetricsCollector', () => { @@ -30,8 +31,10 @@ describe('OsCgroupMetricsCollector', () => { }, }); - const collector = new OsCgroupMetricsCollector({}); + const logger = loggerMock.create(); + const collector = new OsCgroupMetricsCollector({ logger }); expect(await collector.collect()).toEqual({}); + expect(logger.error).not.toHaveBeenCalled(); }); it('collects default cgroup data', async () => { @@ -51,7 +54,7 @@ throttled_time 666 `, }); - const collector = new OsCgroupMetricsCollector({}); + const collector = new OsCgroupMetricsCollector({ logger: loggerMock.create() }); expect(await collector.collect()).toMatchInlineSnapshot(` Object { "cpu": Object { @@ -90,6 +93,7 @@ throttled_time 666 }); const collector = new OsCgroupMetricsCollector({ + logger: loggerMock.create(), cpuAcctPath: 'xxcustomcpuacctxx', cpuPath: 'xxcustomcpuxx', }); @@ -112,4 +116,23 @@ throttled_time 666 } `); }); + + it('returns empty object and logs error on an EACCES error', async () => { + mockFs({ + '/proc/self/cgroup': ` +123:memory:/groupname +123:cpu:/groupname +123:cpuacct:/groupname + `, + '/sys/fs/cgroup': mockFs.directory({ mode: parseInt('0000', 8) }), + }); + + const logger = loggerMock.create(); + + const collector = new OsCgroupMetricsCollector({ logger }); + expect(await collector.collect()).toEqual({}); + expect(logger.error).toHaveBeenCalledWith( + "cgroup metrics could not be read due to error: [Error: EACCES, permission denied '/sys/fs/cgroup/cpuacct/groupname/cpuacct.usage']" + ); + }); }); diff --git a/src/core/server/metrics/collectors/cgroup.ts b/src/core/server/metrics/collectors/cgroup.ts index 867ea44dff1ae..42f5d30d115fe 100644 --- a/src/core/server/metrics/collectors/cgroup.ts +++ b/src/core/server/metrics/collectors/cgroup.ts @@ -19,11 +19,13 @@ import fs from 'fs'; import { join as joinPath } from 'path'; +import { Logger } from '@kbn/logging'; import { MetricsCollector, OpsOsMetrics } from './types'; type OsCgroupMetrics = Pick; interface OsCgroupMetricsCollectorOptions { + logger: Logger; cpuPath?: string; cpuAcctPath?: string; } @@ -38,8 +40,12 @@ export class OsCgroupMetricsCollector implements MetricsCollector { try { + if (this.noCgroupPresent) { + return {}; + } + await this.initializePaths(); - if (this.noCgroupPresent || !this.cpuAcctPath || !this.cpuPath) { + if (!this.cpuAcctPath || !this.cpuPath) { return {}; } @@ -64,12 +70,15 @@ export class OsCgroupMetricsCollector implements MetricsCollector (cb: Function) => cb(null, { dist: 'distrib', release: 'release' })); +import { loggerMock } from '@kbn/logging/target/mocks'; import os from 'os'; import { cgroupCollectorMock } from './os.test.mocks'; import { OsMetricsCollector } from './os'; @@ -27,7 +28,7 @@ describe('OsMetricsCollector', () => { let collector: OsMetricsCollector; beforeEach(() => { - collector = new OsMetricsCollector(); + collector = new OsMetricsCollector({ logger: loggerMock.create() }); cgroupCollectorMock.collect.mockReset(); cgroupCollectorMock.reset.mockReset(); }); diff --git a/src/core/server/metrics/collectors/os.ts b/src/core/server/metrics/collectors/os.ts index eae49278405a9..a9d727e57aaf9 100644 --- a/src/core/server/metrics/collectors/os.ts +++ b/src/core/server/metrics/collectors/os.ts @@ -20,12 +20,14 @@ import os from 'os'; import getosAsync, { LinuxOs } from 'getos'; import { promisify } from 'util'; +import { Logger } from '@kbn/logging'; import { OpsOsMetrics, MetricsCollector } from './types'; import { OsCgroupMetricsCollector } from './cgroup'; const getos = promisify(getosAsync); export interface OpsMetricsCollectorOptions { + logger: Logger; cpuPath?: string; cpuAcctPath?: string; } @@ -33,8 +35,11 @@ export interface OpsMetricsCollectorOptions { export class OsMetricsCollector implements MetricsCollector { private readonly cgroupCollector: OsCgroupMetricsCollector; - constructor(options: OpsMetricsCollectorOptions = {}) { - this.cgroupCollector = new OsCgroupMetricsCollector(options); + constructor(options: OpsMetricsCollectorOptions) { + this.cgroupCollector = new OsCgroupMetricsCollector({ + ...options, + logger: options.logger.get('cgroup'), + }); } public async collect(): Promise { diff --git a/src/core/server/metrics/metrics_service.ts b/src/core/server/metrics/metrics_service.ts index ab58a75d49a98..d3495f2748c71 100644 --- a/src/core/server/metrics/metrics_service.ts +++ b/src/core/server/metrics/metrics_service.ts @@ -50,7 +50,10 @@ export class MetricsService .pipe(first()) .toPromise(); - this.metricsCollector = new OpsMetricsCollector(http.server, config.cGroupOverrides); + this.metricsCollector = new OpsMetricsCollector(http.server, { + logger: this.logger, + ...config.cGroupOverrides, + }); await this.refreshMetrics(); diff --git a/src/core/server/metrics/ops_metrics_collector.test.ts b/src/core/server/metrics/ops_metrics_collector.test.ts index 7aa3f7cd3baf0..c748d1cce12e4 100644 --- a/src/core/server/metrics/ops_metrics_collector.test.ts +++ b/src/core/server/metrics/ops_metrics_collector.test.ts @@ -17,6 +17,7 @@ * under the License. */ +import { loggerMock } from '@kbn/logging/target/mocks'; import { mockOsCollector, mockProcessCollector, @@ -30,7 +31,7 @@ describe('OpsMetricsCollector', () => { beforeEach(() => { const hapiServer = httpServiceMock.createInternalSetupContract().server; - collector = new OpsMetricsCollector(hapiServer, {}); + collector = new OpsMetricsCollector(hapiServer, { logger: loggerMock.create() }); mockOsCollector.collect.mockResolvedValue('osMetrics'); }); diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 7e001ffe28100..e9b345317e999 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -54,6 +54,7 @@ export { metricsServiceMock } from './metrics/metrics_service.mock'; export { renderingMock } from './rendering/rendering_service.mock'; export { statusServiceMock } from './status/status_service.mock'; export { contextServiceMock } from './context/context_service.mock'; +export { capabilitiesServiceMock } from './capabilities/capabilities_service.mock'; export function pluginInitializerContextConfigMock(config: T) { const globalConfig: SharedGlobalConfig = { diff --git a/src/core/server/rendering/__mocks__/params.ts b/src/core/server/rendering/__mocks__/params.ts index 0901cec768cd2..ae3830f703a53 100644 --- a/src/core/server/rendering/__mocks__/params.ts +++ b/src/core/server/rendering/__mocks__/params.ts @@ -20,19 +20,16 @@ import { mockCoreContext } from '../../core_context.mock'; import { httpServiceMock } from '../../http/http_service.mock'; import { pluginServiceMock } from '../../plugins/plugins_service.mock'; -import { legacyServiceMock } from '../../legacy/legacy_service.mock'; import { statusServiceMock } from '../../status/status_service.mock'; const context = mockCoreContext.create(); const http = httpServiceMock.createInternalSetupContract(); const uiPlugins = pluginServiceMock.createUiPlugins(); -const legacyPlugins = legacyServiceMock.createDiscoverPlugins(); const status = statusServiceMock.createInternalSetupContract(); export const mockRenderingServiceParams = context; export const mockRenderingSetupDeps = { http, - legacyPlugins, uiPlugins, status, }; diff --git a/src/core/server/rendering/__mocks__/rendering_service.ts b/src/core/server/rendering/__mocks__/rendering_service.ts index 179a09b8619b0..01d084f9ae53c 100644 --- a/src/core/server/rendering/__mocks__/rendering_service.ts +++ b/src/core/server/rendering/__mocks__/rendering_service.ts @@ -27,11 +27,9 @@ export const setupMock: jest.Mocked = { render: jest.fn(), }; export const mockSetup = jest.fn().mockResolvedValue(setupMock); -export const mockStart = jest.fn(); export const mockStop = jest.fn(); export const mockRenderingService: jest.Mocked = { setup: mockSetup, - start: mockStart, stop: mockStop, }; export const RenderingService = jest.fn( diff --git a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap index ab828a1780425..07ca59a48c6b0 100644 --- a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap +++ b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap @@ -27,15 +27,6 @@ Object { "translationsUrl": "/mock-server-basepath/translations/en.json", }, "legacyMetadata": Object { - "app": Object {}, - "basePath": "/mock-server-basepath", - "branch": Any, - "buildNum": Any, - "buildSha": Any, - "bundleId": "app:core", - "devMode": true, - "nav": Array [], - "serverName": "http-server-test", "uiSettings": Object { "defaults": Object { "registered": Object { @@ -44,7 +35,6 @@ Object { }, "user": Object {}, }, - "version": Any, }, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], @@ -80,15 +70,6 @@ Object { "translationsUrl": "/mock-server-basepath/translations/en.json", }, "legacyMetadata": Object { - "app": Object {}, - "basePath": "/mock-server-basepath", - "branch": Any, - "buildNum": Any, - "buildSha": Any, - "bundleId": "app:core", - "devMode": true, - "nav": Array [], - "serverName": "http-server-test", "uiSettings": Object { "defaults": Object { "registered": Object { @@ -97,7 +78,6 @@ Object { }, "user": Object {}, }, - "version": Any, }, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], @@ -133,15 +113,6 @@ Object { "translationsUrl": "/mock-server-basepath/translations/en.json", }, "legacyMetadata": Object { - "app": Object {}, - "basePath": "/mock-server-basepath", - "branch": Any, - "buildNum": Any, - "buildSha": Any, - "bundleId": "app:core", - "devMode": true, - "nav": Array [], - "serverName": "http-server-test", "uiSettings": Object { "defaults": Object { "registered": Object { @@ -154,7 +125,6 @@ Object { }, }, }, - "version": Any, }, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], @@ -190,15 +160,6 @@ Object { "translationsUrl": "/translations/en.json", }, "legacyMetadata": Object { - "app": Object {}, - "basePath": "", - "branch": Any, - "buildNum": Any, - "buildSha": Any, - "bundleId": "app:core", - "devMode": true, - "nav": Array [], - "serverName": "http-server-test", "uiSettings": Object { "defaults": Object { "registered": Object { @@ -207,7 +168,6 @@ Object { }, "user": Object {}, }, - "version": Any, }, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], @@ -243,15 +203,6 @@ Object { "translationsUrl": "/mock-server-basepath/translations/en.json", }, "legacyMetadata": Object { - "app": Object {}, - "basePath": "/mock-server-basepath", - "branch": Any, - "buildNum": Any, - "buildSha": Any, - "bundleId": "app:core", - "devMode": true, - "nav": Array [], - "serverName": "http-server-test", "uiSettings": Object { "defaults": Object { "registered": Object { @@ -260,7 +211,6 @@ Object { }, "user": Object {}, }, - "version": Any, }, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index 254bafed5b194..08978cd1df64d 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -43,12 +43,6 @@ const INJECTED_METADATA = { version: expect.any(String), }, }, - legacyMetadata: { - branch: expect.any(String), - buildNum: expect.any(Number), - buildSha: expect.any(String), - version: expect.any(String), - }, }; const { createKibanaRequest, createRawRequest } = httpServerMock; @@ -72,13 +66,6 @@ describe('RenderingService', () => { registered: { name: 'title' }, }); render = (await service.setup(mockRenderingSetupDeps)).render; - await service.start({ - legacy: { - legacyInternals: { - getVars: () => ({}), - }, - }, - } as any); }); it('renders "core" page', async () => { diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index 7761c89044f6f..738787f940905 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -20,14 +20,11 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { take } from 'rxjs/operators'; - import { i18n } from '@kbn/i18n'; import { UiPlugins } from '../plugins'; -import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; import { Template } from './views'; -import { LegacyService } from '../legacy'; import { IRenderOptions, RenderingSetupDeps, @@ -36,25 +33,20 @@ import { } from './types'; /** @internal */ -export class RenderingService implements CoreService { - private legacyInternals?: LegacyService['legacyInternals']; +export class RenderingService { constructor(private readonly coreContext: CoreContext) {} public async setup({ http, status, - legacyPlugins, uiPlugins, }: RenderingSetupDeps): Promise { return { render: async ( request, uiSettings, - { app = { getId: () => 'core' }, includeUserSettings = true, vars }: IRenderOptions = {} + { includeUserSettings = true, vars }: IRenderOptions = {} ) => { - if (!this.legacyInternals) { - throw new Error('Cannot render before "start"'); - } const env = { mode: this.coreContext.env.mode, packageInfo: this.coreContext.env.packageInfo, @@ -65,7 +57,6 @@ export class RenderingService implements CoreService ({ id, @@ -96,16 +87,6 @@ export class RenderingService implements CoreService; }>; legacyMetadata: { - app: { getId(): string }; - bundleId: string; - nav: LegacyNavLink[]; - version: string; - branch: string; - buildNum: number; - buildSha: string; - serverName: string; - devMode: boolean; - basePath: string; uiSettings: { defaults: Record; user: Record>; @@ -78,7 +67,6 @@ export interface RenderingMetadata { /** @internal */ export interface RenderingSetupDeps { http: InternalHttpServiceSetup; - legacyPlugins: LegacyServiceDiscoverPlugins; status: InternalStatusServiceSetup; uiPlugins: UiPlugins; } @@ -91,14 +79,6 @@ export interface IRenderOptions { */ includeUserSettings?: boolean; - /** - * Render the bootstrapped HTML content for an optional legacy application. - * Defaults to `core`. - * @deprecated for legacy use only, remove with ui_render_mixin - * @internal - */ - app?: { getId(): string }; - /** * Inject custom vars into the page metadata. * @deprecated for legacy use only, remove with ui_render_mixin diff --git a/src/core/server/saved_objects/migrations/core/migration_es_client.ts b/src/core/server/saved_objects/migrations/core/migration_es_client.ts index ff859057f8fe8..e8482e6352a82 100644 --- a/src/core/server/saved_objects/migrations/core/migration_es_client.ts +++ b/src/core/server/saved_objects/migrations/core/migration_es_client.ts @@ -80,7 +80,7 @@ export function createMigrationEsClient( throw new Error(`unknown ElasticsearchClient client method [${key}]`); } return await migrationRetryCallCluster( - () => fn(params, { maxRetries: 0, ...options }), + () => fn.call(client, params, { maxRetries: 0, ...options }), log, delay ); diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 8a764d9bd2f66..cc51d27589ce7 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -864,10 +864,6 @@ export interface IndexSettingsDeprecationInfo { // @public (undocumented) export interface IRenderOptions { - // @internal @deprecated - app?: { - getId(): string; - }; includeUserSettings?: boolean; // @internal @deprecated vars?: Record; @@ -1286,21 +1282,6 @@ export class LegacyElasticsearchErrorHelpers { static isNotAuthorizedError(error: any): error is LegacyElasticsearchError; } -// Warning: (ae-forgotten-export) The symbol "ILegacyInternals" needs to be exported by the entry point index.d.ts -// -// @internal @deprecated (undocumented) -export class LegacyInternals implements ILegacyInternals { - constructor(uiExports: LegacyUiExports, config: LegacyConfig, server: Server); - // (undocumented) - getInjectedUiAppVars(id: string): Promise>; - // (undocumented) - getVars(id: string, request: KibanaRequest | LegacyRequest, injected?: LegacyVars): Promise>; - // Warning: (ae-forgotten-export) The symbol "VarsInjector" needs to be exported by the entry point index.d.ts - // - // (undocumented) - injectUiAppVars(id: string, injector: VarsInjector): void; - } - // @public @deprecated (undocumented) export interface LegacyRequest extends Request { } @@ -1312,16 +1293,6 @@ export class LegacyScopedClusterClient implements ILegacyScopedClusterClient { callAsInternalUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; } -// Warning: (ae-forgotten-export) The symbol "LegacyPlugins" needs to be exported by the entry point index.d.ts -// -// @internal @deprecated (undocumented) -export interface LegacyServiceDiscoverPlugins extends LegacyPlugins { - // (undocumented) - pluginExtendedConfig: LegacyConfig; - // (undocumented) - settings: LegacyVars; -} - // @public @deprecated (undocumented) export interface LegacyServiceSetupDeps { // Warning: (ae-forgotten-export) The symbol "LegacyCoreSetup" needs to be exported by the entry point index.d.ts @@ -1346,31 +1317,6 @@ export interface LegacyServiceStartDeps { plugins: Record; } -// @internal @deprecated (undocumented) -export interface LegacyUiExports { - // Warning: (ae-forgotten-export) The symbol "VarsProvider" needs to be exported by the entry point index.d.ts - // - // (undocumented) - defaultInjectedVarProviders?: VarsProvider[]; - // Warning: (ae-forgotten-export) The symbol "VarsReplacer" needs to be exported by the entry point index.d.ts - // - // (undocumented) - injectedVarsReplacers?: VarsReplacer[]; - // Warning: (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts - // - // (undocumented) - navLinkSpecs?: LegacyNavLinkSpec[] | null; - // Warning: (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts - // - // (undocumented) - uiAppSpecs?: Array; - // (undocumented) - unknown?: [{ - pluginSpec: LegacyPluginSpec; - type: unknown; - }]; -} - // Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts // // @public @@ -2734,7 +2680,6 @@ export const validBodyOutput: readonly ["data", "stream"]; // Warnings were encountered during analysis: // // src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:135:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:277:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 8502f563cb0c2..5935636d54f9d 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -113,11 +113,12 @@ export class Server { const { pluginTree, uiPlugins } = await this.plugins.discover({ environment: environmentSetup, }); - const legacyPlugins = await this.legacy.discoverPlugins(); + const legacyConfigSetup = await this.legacy.setupLegacyConfig(); // Immediately terminate in case of invalid configuration + // This needs to be done after plugin discovery await this.configService.validate(); - await ensureValidConfiguration(this.configService, legacyPlugins); + await ensureValidConfiguration(this.configService, legacyConfigSetup); const contextServiceSetup = this.context.setup({ // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins: @@ -166,7 +167,6 @@ export class Server { const renderingSetup = await this.rendering.setup({ http: httpSetup, status: statusSetup, - legacyPlugins, uiPlugins, }); @@ -248,10 +248,6 @@ export class Server { await this.http.start(); - await this.rendering.start({ - legacy: this.legacy, - }); - return this.coreStart; } diff --git a/src/core/server/status/plugins_status.test.ts b/src/core/server/status/plugins_status.test.ts index a75dc8c283698..176e2414a8d04 100644 --- a/src/core/server/status/plugins_status.test.ts +++ b/src/core/server/status/plugins_status.test.ts @@ -161,13 +161,13 @@ describe('PluginStatusService', () => { }, b: { level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '[savedObjects]: savedObjects degraded', detail: 'See the status page for more information', meta: expect.any(Object), }, c: { level: ServiceStatusLevels.degraded, - summary: '[3] services are degraded', + summary: '[savedObjects]: savedObjects degraded', detail: 'See the status page for more information', meta: expect.any(Object), }, @@ -186,13 +186,13 @@ describe('PluginStatusService', () => { }, b: { level: ServiceStatusLevels.critical, - summary: '[2] services are critical', + summary: '[elasticsearch]: elasticsearch critical', detail: 'See the status page for more information', meta: expect.any(Object), }, c: { level: ServiceStatusLevels.critical, - summary: '[3] services are critical', + summary: '[elasticsearch]: elasticsearch critical', detail: 'See the status page for more information', meta: expect.any(Object), }, diff --git a/src/core/server/status/plugins_status.ts b/src/core/server/status/plugins_status.ts index 113d59b327c11..988f2d9969ccb 100644 --- a/src/core/server/status/plugins_status.ts +++ b/src/core/server/status/plugins_status.ts @@ -33,7 +33,17 @@ interface Deps { export class PluginsStatusService { private readonly pluginStatuses = new Map>(); private readonly update$ = new BehaviorSubject(true); - constructor(private readonly deps: Deps) {} + private readonly defaultInheritedStatus$: Observable; + + constructor(private readonly deps: Deps) { + this.defaultInheritedStatus$ = this.deps.core$.pipe( + map((coreStatus) => { + return getSummaryStatus(Object.entries(coreStatus), { + allAvailableSummary: `All dependencies are available`, + }); + }) + ); + } public set(plugin: PluginName, status$: Observable) { this.pluginStatuses.set(plugin, status$); @@ -57,14 +67,24 @@ export class PluginsStatusService { } public getDerivedStatus$(plugin: PluginName): Observable { - return combineLatest([this.deps.core$, this.getDependenciesStatus$(plugin)]).pipe( - map(([coreStatus, pluginStatuses]) => { - return getSummaryStatus( - [...Object.entries(coreStatus), ...Object.entries(pluginStatuses)], - { - allAvailableSummary: `All dependencies are available`, - } - ); + return this.update$.pipe( + switchMap(() => { + // Only go up the dependency tree if any of this plugin's dependencies have a custom status + // Helps eliminate memory overhead of creating thousands of Observables unnecessarily. + if (this.anyCustomStatuses(plugin)) { + return combineLatest([this.deps.core$, this.getDependenciesStatus$(plugin)]).pipe( + map(([coreStatus, pluginStatuses]) => { + return getSummaryStatus( + [...Object.entries(coreStatus), ...Object.entries(pluginStatuses)], + { + allAvailableSummary: `All dependencies are available`, + } + ); + }) + ); + } else { + return this.defaultInheritedStatus$; + } }) ); } @@ -95,4 +115,17 @@ export class PluginsStatusService { }) ); } + + /** + * Determines whether or not this plugin or any plugin in it's dependency tree have a custom status registered. + */ + private anyCustomStatuses(plugin: PluginName): boolean { + if (this.pluginStatuses.get(plugin)) { + return true; + } + + return this.deps.pluginDependencies + .get(plugin)! + .reduce((acc, depName) => acc || this.anyCustomStatuses(depName), false as boolean); + } } diff --git a/src/core/server/status/status_service.ts b/src/core/server/status/status_service.ts index 9acf93f2f8197..62f226405e81a 100644 --- a/src/core/server/status/status_service.ts +++ b/src/core/server/status/status_service.ts @@ -70,10 +70,10 @@ export class StatusService implements CoreService { const core$ = this.setupCoreStatus({ elasticsearch, savedObjects }); this.pluginsStatus = new PluginsStatusService({ core$, pluginDependencies }); - const overall$: Observable = combineLatest( + const overall$: Observable = combineLatest([ core$, - this.pluginsStatus.getAll$() - ).pipe( + this.pluginsStatus.getAll$(), + ]).pipe( // Prevent many emissions at once from dependency status resolution from making this too noisy debounceTime(500), map(([coreStatus, pluginsStatus]) => { diff --git a/src/core/typings.ts b/src/core/typings.ts index a84e1c01d2bd2..f271d0b03e0d3 100644 --- a/src/core/typings.ts +++ b/src/core/typings.ts @@ -17,34 +17,6 @@ * under the License. */ -declare module 'query-string' { - type ArrayFormat = 'bracket' | 'index' | 'none'; - - export interface ParseOptions { - arrayFormat?: ArrayFormat; - sort: ((itemLeft: string, itemRight: string) => number) | false; - } - - export interface ParsedQuery { - [key: string]: T | T[] | null | undefined; - } - - export function parse(str: string, options?: ParseOptions): ParsedQuery; - - export function parseUrl(str: string, options?: ParseOptions): { url: string; query: any }; - - export interface StringifyOptions { - strict?: boolean; - encode?: boolean; - arrayFormat?: ArrayFormat; - sort: ((itemLeft: string, itemRight: string) => number) | false; - } - - export function stringify(obj: object, options?: StringifyOptions): string; - - export function extract(str: string): string; -} - type DeeplyMockedKeys = { [P in keyof T]: T[P] extends (...args: any[]) => any ? jest.MockInstance, Parameters> diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index b02b7cc16ec4a..884e7e38494cc 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -235,6 +235,7 @@ kibana_vars=( xpack.security.cookieName xpack.security.enabled xpack.security.encryptionKey + xpack.security.sameSiteCookies xpack.security.secureCookies xpack.security.sessionTimeout xpack.security.session.idleTimeout diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index 5d31db63773fa..3c556a4f1ba3c 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -55,7 +55,6 @@ export default { '@elastic/eui$': '/node_modules/@elastic/eui/test-env', '@elastic/eui/lib/(.*)?': '/node_modules/@elastic/eui/test-env/$1', '^src/plugins/(.*)': '/src/plugins/$1', - '^uiExports/(.*)': '/src/dev/jest/mocks/file_mock.js', '^test_utils/(.*)': '/src/test_utils/public/$1', '^fixtures/(.*)': '/src/fixtures/$1', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': diff --git a/src/dev/jest/setup/react_testing_library.js b/src/dev/jest/setup/react_testing_library.js index 41f58354844a3..84b5b6096e79b 100644 --- a/src/dev/jest/setup/react_testing_library.js +++ b/src/dev/jest/setup/react_testing_library.js @@ -29,4 +29,4 @@ import '@testing-library/jest-dom'; import { configure } from '@testing-library/react/pure'; // instead of default 'data-testid', use kibana's 'data-test-subj' -configure({ testIdAttribute: 'data-test-subj' }); +configure({ testIdAttribute: 'data-test-subj', asyncUtilTimeout: 4500 }); diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 9d9f5616b5a33..d31a408e98c67 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -23,7 +23,7 @@ export const storybookAliases = { codeeditor: 'src/plugins/kibana_react/public/code_editor/scripts/storybook.ts', dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/scripts/storybook.js', embeddable: 'src/plugins/embeddable/scripts/storybook.js', - infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js', + infra: 'x-pack/plugins/infra/scripts/storybook.js', security_solution: 'x-pack/plugins/security_solution/scripts/storybook.js', ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/scripts/storybook.js', observability: 'x-pack/plugins/observability/scripts/storybook.js', diff --git a/src/fixtures/stubbed_saved_object_index_pattern.ts b/src/fixtures/stubbed_saved_object_index_pattern.ts index 44b391f14cf9c..261e451db5452 100644 --- a/src/fixtures/stubbed_saved_object_index_pattern.ts +++ b/src/fixtures/stubbed_saved_object_index_pattern.ts @@ -28,10 +28,10 @@ export function stubbedSavedObjectIndexPattern(id: string | null = null) { type: 'index-pattern', attributes: { timeFieldName: 'timestamp', - customFormats: '{}', + customFormats: {}, fields: mockLogstashFields, title: 'title', }, - version: 2, + version: '2', }; } diff --git a/src/fixtures/telemetry_collectors/constants.ts b/src/fixtures/telemetry_collectors/constants.ts index 4aac9e66cdbdb..d4c9a1f85c4d7 100644 --- a/src/fixtures/telemetry_collectors/constants.ts +++ b/src/fixtures/telemetry_collectors/constants.ts @@ -51,3 +51,7 @@ export const externallyDefinedSchema: MakeSchemaFrom<{ locale: string }> = { type: 'keyword', }, }; + +export type TypeAliasWithUnion = Usage & WithUnion; + +export type TypeAliasWithRecord = Usage & Record; diff --git a/src/legacy/plugin_discovery/README.md b/src/legacy/plugin_discovery/README.md deleted file mode 100644 index 83e7c10d16fff..0000000000000 --- a/src/legacy/plugin_discovery/README.md +++ /dev/null @@ -1,148 +0,0 @@ -# Plugin Discovery - -The plugin discovery module defines the core plugin loading logic used by the Kibana server. It exports functions for - - -## `findPluginSpecs(settings, [config])` - -Finds [`PluginSpec`][PluginSpec] objects - -### params - - `settings`: the same settings object accepted by [`KbnServer`][KbnServer] - - `[config]`: Optional - a [`Config`][Config] service. Using this param causes `findPluginSpecs()` to modify `config`'s schema to support the configuration for each discovered [`PluginSpec`][PluginSpec]. If you can, please use the [`Config`][Config] service produced by `extendedConfig$` rather than passing in an existing service so that `findPluginSpecs()` is side-effect free. - -### return value - -`findPluginSpecs()` returns an object of Observables which produce values at different parts of the process. Since the Observables are all aware of their own dependencies you can subscribe to any combination (within the same tick) and only the necessary plugin logic will be executed. - -If you *never* subscribe to any of the Observables then plugin discovery won't actually run. - - - `pack$`: emits every [`PluginPack`][PluginPack] found - - `invalidDirectoryError$: Observable`: emits [`InvalidDirectoryError`][Errors]s caused by `settings.plugins.scanDirs` values that don't point to actual directories. `findPluginSpecs()` will not abort when this error is encountered. - - `invalidPackError$: Observable`: emits [`InvalidPackError`][Errors]s caused by children of `settings.plugins.scanDirs` or `settings.plugins.paths` values which don't meet the requirements of a [`PluginPack`][PluginPack] (probably missing a `package.json`). `findPluginSpecs()` will not abort when this error is encountered. - - `deprecation$: Observable`: emits deprecation warnings that are produces when reading each [`PluginPack`][PluginPack]'s configuration - - `extendedConfig$: Observable`: emits the [`Config`][Config] service that was passed to `findPluginSpecs()` (or created internally if none was passed) after it has been extended with the configuration from each plugin - - `spec$: Observable`: emits every *enabled* [`PluginSpec`][PluginSpec] defined by the discovered [`PluginPack`][PluginPack]s - - `disabledSpec$: Observable`: emits every *disabled* [`PluginSpec`][PluginSpec] defined by the discovered [`PluginPack`][PluginPack]s - - `invalidVersionSpec$: Observable`: emits every [`PluginSpec`][PluginSpec] who's required kibana version does not match the version exposed by `config.get('pkg.version')` - -### example - -Just get the plugin specs, only fail if there is an uncaught error of some sort: -```js -const { pack$ } = findPluginSpecs(settings); -const packs = await pack$.pipe(toArray()).toPromise() -``` - -Just log the deprecation messages: -```js -const { deprecation$ } = findPluginSpecs(settings); -for (const warning of await deprecation$.pipe(toArray()).toPromise()) { - console.log('DEPRECATION:', warning) -} -``` - -Get the packs but fail if any packs are invalid: -```js -const { pack$, invalidDirectoryError$ } = findPluginSpecs(settings); -const packs = await Rx.merge( - pack$.pipe(toArray()), - - // if we ever get an InvalidDirectoryError, throw it - // into the stream so that all streams are unsubscribed, - // the discovery process is aborted, and the promise rejects - invalidDirectoryError$.pipe( - map(error => { throw error }) - ), -).toPromise() -``` - -Handle everything -```js -const { - pack$, - invalidDirectoryError$, - invalidPackError$, - deprecation$, - extendedConfig$, - spec$, - disabledSpecs$, - invalidVersionSpec$, -} = findPluginSpecs(settings); - -Rx.merge( - pack$.pipe( - tap(pluginPack => console.log('Found plugin pack', pluginPack)) - ), - - invalidDirectoryError$.pipe( - tap(error => console.log('Invalid directory error', error)) - ), - - invalidPackError$.pipe( - tap(error => console.log('Invalid plugin pack error', error)) - ), - - deprecation$.pipe( - tap(msg => console.log('DEPRECATION:', msg)) - ), - - extendedConfig$.pipe( - tap(config => console.log('config service extended by plugins', config)) - ), - - spec$.pipe( - tap(pluginSpec => console.log('enabled plugin spec found', spec)) - ), - - disabledSpec$.pipe( - tap(pluginSpec => console.log('disabled plugin spec found', spec)) - ), - - invalidVersionSpec$.pipe( - tap(pluginSpec => console.log('plugin spec with invalid version found', spec)) - ), -) -.toPromise() -.then(() => { - console.log('plugin discovery complete') -}) -.catch((error) => { - console.log('plugin discovery failed', error) -}) - -``` - -## `reduceExportSpecs(pluginSpecs, reducers, [defaults={}])` - -Reduces every value exported by the [`PluginSpec`][PluginSpec]s to produce a single value. If an exported value is an array each item in the array will be reduced individually. If the exported value is `undefined` it will be ignored. The reducer is called with the signature: - -```js -reducer( - // the result of the previous reducer call, or `defaults` - acc: any, - // the exported value, found at `uiExports[type]` or `uiExports[type][i]` - // in the PluginSpec config. - spec: any, - // the key in `uiExports` where this export was found - type: string, - // the PluginSpec which exported this spec - pluginSpec: PluginSpec -) -``` - -## `new PluginPack(options)` class - -Only exported so that `PluginPack` instances can be created in tests and used in place of on-disk plugin fixtures. Use `findPluginSpecs()`, or the cached result of a call to `findPluginSpecs()` (like `kbnServer.pluginSpecs`) any time you might need access to `PluginPack` objects in distributed code. - -### params - - - `options.path`: absolute path to where this plugin pack was found, this is normally a direct child of `./src/legacy/core_plugins` or `./plugins` - - `options.pkg`: the parsed `package.json` for this pack, used for defaults in `PluginSpec` objects defined by this pack - - `options.provider`: the default export of the pack, a function which is called with the `PluginSpec` class which should return one or more `PluginSpec` objects. - -[PluginPack]: ./plugin_pack/plugin_pack.js "PluginPath class definition" -[PluginSpec]: ./plugin_spec/plugin_spec.js "PluginSpec class definition" -[Errors]: ./errors.js "PluginDiscover specific error types" -[KbnServer]: ../server/kbn_server.js "KbnServer class definition" -[Config]: ../server/config/config.js "KbnServer/Config class definition" diff --git a/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js b/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js deleted file mode 100644 index e6af23d69c549..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { toArray } from 'rxjs/operators'; - -import expect from '@kbn/expect'; -import { isEqual } from 'lodash'; -import { findPluginSpecs } from '../find_plugin_specs'; -import { PluginSpec } from '../plugin_spec'; - -const PLUGIN_FIXTURES = resolve(__dirname, 'fixtures/plugins'); -const CONFLICT_FIXTURES = resolve(__dirname, 'fixtures/conflicts'); - -describe('plugin discovery', () => { - describe('findPluginSpecs()', function () { - this.timeout(10000); - - describe('spec$', () => { - it('finds specs for specified plugin paths', async () => { - const { spec$ } = findPluginSpecs({ - plugins: { - paths: [ - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'broken'), - ], - }, - }); - - const specs = await spec$.pipe(toArray()).toPromise(); - expect(specs).to.have.length(3); - specs.forEach((spec) => { - expect(spec).to.be.a(PluginSpec); - }); - expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); - }); - - it('finds all specs in scanDirs', async () => { - const { spec$ } = findPluginSpecs({ - // used to ensure the dev_mode plugin is enabled - env: 'development', - - plugins: { - scanDirs: [PLUGIN_FIXTURES], - }, - }); - - const specs = await spec$.pipe(toArray()).toPromise(); - expect(specs).to.have.length(3); - specs.forEach((spec) => { - expect(spec).to.be.a(PluginSpec); - }); - expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); - }); - - it('does not find disabled plugins', async () => { - const { spec$ } = findPluginSpecs({ - 'bar:one': { - enabled: false, - }, - - plugins: { - paths: [ - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'broken'), - ], - }, - }); - - const specs = await spec$.pipe(toArray()).toPromise(); - expect(specs).to.have.length(2); - specs.forEach((spec) => { - expect(spec).to.be.a(PluginSpec); - }); - expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:two', 'foo']); - }); - - it('dedupes duplicate packs', async () => { - const { spec$ } = findPluginSpecs({ - plugins: { - scanDirs: [PLUGIN_FIXTURES], - paths: [ - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'broken'), - resolve(PLUGIN_FIXTURES, 'broken'), - ], - }, - }); - - const specs = await spec$.pipe(toArray()).toPromise(); - expect(specs).to.have.length(3); - specs.forEach((spec) => { - expect(spec).to.be.a(PluginSpec); - }); - expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); - }); - - describe('conflicting plugin spec ids', () => { - it('fails with informative message', async () => { - const { spec$ } = findPluginSpecs({ - plugins: { - scanDirs: [], - paths: [resolve(CONFLICT_FIXTURES, 'foo')], - }, - }); - - try { - await spec$.pipe(toArray()).toPromise(); - throw new Error('expected spec$ to throw an error'); - } catch (error) { - expect(error.message).to.contain('Multiple plugins found with the id "foo"'); - expect(error.message).to.contain(CONFLICT_FIXTURES); - } - }); - }); - }); - - describe('packageJson$', () => { - const checkPackageJsons = (packageJsons) => { - expect(packageJsons).to.have.length(2); - const package1 = packageJsons.find((packageJson) => - isEqual( - { - directoryPath: resolve(PLUGIN_FIXTURES, 'foo'), - contents: { - name: 'foo', - version: 'kibana', - }, - }, - packageJson - ) - ); - expect(package1).to.be.an(Object); - const package2 = packageJsons.find((packageJson) => - isEqual( - { - directoryPath: resolve(PLUGIN_FIXTURES, 'bar'), - contents: { - name: 'foo', - version: 'kibana', - }, - }, - packageJson - ) - ); - expect(package2).to.be.an(Object); - }; - - it('finds packageJson for specified plugin paths', async () => { - const { packageJson$ } = findPluginSpecs({ - plugins: { - paths: [ - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'broken'), - ], - }, - }); - - const packageJsons = await packageJson$.pipe(toArray()).toPromise(); - checkPackageJsons(packageJsons); - }); - - it('finds all packageJsons in scanDirs', async () => { - const { packageJson$ } = findPluginSpecs({ - // used to ensure the dev_mode plugin is enabled - env: 'development', - - plugins: { - scanDirs: [PLUGIN_FIXTURES], - }, - }); - - const packageJsons = await packageJson$.pipe(toArray()).toPromise(); - checkPackageJsons(packageJsons); - }); - - it('dedupes duplicate packageJson', async () => { - const { packageJson$ } = findPluginSpecs({ - plugins: { - scanDirs: [PLUGIN_FIXTURES], - paths: [ - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'broken'), - resolve(PLUGIN_FIXTURES, 'broken'), - ], - }, - }); - - const packageJsons = await packageJson$.pipe(toArray()).toPromise(); - checkPackageJsons(packageJsons); - }); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js deleted file mode 100644 index fcbe3487463b7..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default function (kibana) { - return [ - // two plugins exported without ids will both inherit - // the id of the pack and conflict - new kibana.Plugin({}), - new kibana.Plugin({}), - ]; -} diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/package.json b/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/package.json b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/index.js deleted file mode 100644 index 59f4a2649f019..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default { - foo: 'bar', -}; diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/package1.json b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/package1.json deleted file mode 100644 index 81ddb6221d515..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/package1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "baz", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js deleted file mode 100644 index e43a1dcedb372..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -module.exports = function (kibana) { - return new kibana.Plugin({ - id: 'foo', - }); -}; diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/package.json b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/errors.js b/src/legacy/plugin_discovery/errors.js deleted file mode 100644 index 02d81b32d1fd1..0000000000000 --- a/src/legacy/plugin_discovery/errors.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const errorCodeProperty = Symbol('pluginDiscovery/errorCode'); - -/** - * Thrown when reading a plugin directory fails, wraps failure - * @type {String} - */ -const ERROR_INVALID_DIRECTORY = 'ERROR_INVALID_DIRECTORY'; -export function createInvalidDirectoryError(sourceError, path) { - sourceError[errorCodeProperty] = ERROR_INVALID_DIRECTORY; - sourceError.path = path; - return sourceError; -} -export function isInvalidDirectoryError(error) { - return error && error[errorCodeProperty] === ERROR_INVALID_DIRECTORY; -} - -/** - * Thrown when trying to create a PluginPack for a path that - * is not a valid plugin definition - * @type {String} - */ -const ERROR_INVALID_PACK = 'ERROR_INVALID_PACK'; -export function createInvalidPackError(path, reason) { - const error = new Error(`PluginPack${path ? ` at "${path}"` : ''} ${reason}`); - error[errorCodeProperty] = ERROR_INVALID_PACK; - error.path = path; - return error; -} -export function isInvalidPackError(error) { - return error && error[errorCodeProperty] === ERROR_INVALID_PACK; -} - -/** - * Thrown when trying to load a PluginSpec that is invalid for some reason - * @type {String} - */ -const ERROR_INVALID_PLUGIN = 'ERROR_INVALID_PLUGIN'; -export function createInvalidPluginError(spec, reason) { - const error = new Error( - `Plugin from ${spec.getId()} at ${spec.getPack().getPath()} is invalid because ${reason}` - ); - error[errorCodeProperty] = ERROR_INVALID_PLUGIN; - error.spec = spec; - return error; -} -export function isInvalidPluginError(error) { - return error && error[errorCodeProperty] === ERROR_INVALID_PLUGIN; -} - -/** - * Thrown when trying to load a PluginSpec whose version is incompatible - * @type {String} - */ -const ERROR_INCOMPATIBLE_PLUGIN_VERSION = 'ERROR_INCOMPATIBLE_PLUGIN_VERSION'; -export function createIncompatiblePluginVersionError(spec) { - const error = new Error( - `Plugin ${spec.getId()} is only compatible with Kibana version ${spec.getExpectedKibanaVersion()}` - ); - error[errorCodeProperty] = ERROR_INCOMPATIBLE_PLUGIN_VERSION; - error.spec = spec; - return error; -} -export function isIncompatiblePluginVersionError(error) { - return error && error[errorCodeProperty] === ERROR_INCOMPATIBLE_PLUGIN_VERSION; -} diff --git a/src/legacy/plugin_discovery/find_plugin_specs.js b/src/legacy/plugin_discovery/find_plugin_specs.js deleted file mode 100644 index b97476bb456a5..0000000000000 --- a/src/legacy/plugin_discovery/find_plugin_specs.js +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import * as Rx from 'rxjs'; -import { - distinct, - toArray, - mergeMap, - share, - shareReplay, - filter, - last, - map, - tap, -} from 'rxjs/operators'; -import { realpathSync } from 'fs'; - -import { Config } from '../server/config'; - -import { extendConfigService, disableConfigExtension } from './plugin_config'; - -import { - createPack$, - createPackageJsonAtPath$, - createPackageJsonsInDirectory$, -} from './plugin_pack'; - -import { isInvalidDirectoryError, isInvalidPackError } from './errors'; - -export function defaultConfig(settings) { - return Config.withDefaultSchema(settings); -} - -function bufferAllResults(observable) { - return observable.pipe( - // buffer all results into a single array - toArray(), - // merge the array back into the stream when complete - mergeMap((array) => array) - ); -} - -/** - * Determine a distinct value for each result from find$ - * so they can be deduplicated - * @param {{error?,pack?}} result - * @return {Any} - */ -function getDistinctKeyForFindResult(result) { - // errors are distinct by their message - if (result.error) { - return result.error.message; - } - - // packs are distinct by their absolute and real path - if (result.packageJson) { - return realpathSync(result.packageJson.directoryPath); - } - - // non error/pack results shouldn't exist, but if they do they are all unique - return result; -} - -function groupSpecsById(specs) { - const specsById = new Map(); - for (const spec of specs) { - const id = spec.getId(); - if (specsById.has(id)) { - specsById.get(id).push(spec); - } else { - specsById.set(id, [spec]); - } - } - return specsById; -} - -/** - * Creates a collection of observables for discovering pluginSpecs - * using Kibana's defaults, settings, and config service - * - * @param {Object} settings - * @param {ConfigService} [configToMutate] when supplied **it is mutated** to - * include the config from discovered plugin specs - * @return {Object} - */ -export function findPluginSpecs(settings, configToMutate) { - const config$ = Rx.defer(async () => { - if (configToMutate) { - return configToMutate; - } - - return defaultConfig(settings); - }).pipe(shareReplay()); - - // find plugin packs in configured paths/dirs - const packageJson$ = config$.pipe( - mergeMap((config) => - Rx.merge( - ...config.get('plugins.paths').map(createPackageJsonAtPath$), - ...config.get('plugins.scanDirs').map(createPackageJsonsInDirectory$) - ) - ), - distinct(getDistinctKeyForFindResult), - share() - ); - - const pack$ = createPack$(packageJson$).pipe(share()); - - const extendConfig$ = config$.pipe( - mergeMap((config) => - pack$.pipe( - // get the specs for each found plugin pack - mergeMap(({ pack }) => (pack ? pack.getPluginSpecs() : [])), - // make sure that none of the plugin specs have conflicting ids, fail - // early if conflicts detected or merge the specs back into the stream - toArray(), - mergeMap((allSpecs) => { - for (const [id, specs] of groupSpecsById(allSpecs)) { - if (specs.length > 1) { - throw new Error( - `Multiple plugins found with the id "${id}":\n${specs - .map((spec) => ` - ${id} at ${spec.getPath()}`) - .join('\n')}` - ); - } - } - - return allSpecs; - }), - mergeMap(async (spec) => { - // extend the config service with this plugin spec and - // collect its deprecations messages if some of its - // settings are outdated - const deprecations = []; - await extendConfigService(spec, config, settings, (message) => { - deprecations.push({ spec, message }); - }); - - return { - spec, - deprecations, - }; - }), - // extend the config with all plugins before determining enabled status - bufferAllResults, - map(({ spec, deprecations }) => { - const isRightVersion = spec.isVersionCompatible(config.get('pkg.version')); - const enabled = isRightVersion && spec.isEnabled(config); - return { - config, - spec, - deprecations, - enabledSpecs: enabled ? [spec] : [], - disabledSpecs: enabled ? [] : [spec], - invalidVersionSpecs: isRightVersion ? [] : [spec], - }; - }), - // determine which plugins are disabled before actually removing things from the config - bufferAllResults, - tap((result) => { - for (const spec of result.disabledSpecs) { - disableConfigExtension(spec, config); - } - }) - ) - ), - share() - ); - - return { - // package JSONs found when searching configure paths - packageJson$: packageJson$.pipe( - mergeMap((result) => (result.packageJson ? [result.packageJson] : [])) - ), - - // plugin packs found when searching configured paths - pack$: pack$.pipe(mergeMap((result) => (result.pack ? [result.pack] : []))), - - // errors caused by invalid directories of plugin directories - invalidDirectoryError$: pack$.pipe( - mergeMap((result) => (isInvalidDirectoryError(result.error) ? [result.error] : [])) - ), - - // errors caused by directories that we expected to be plugin but were invalid - invalidPackError$: pack$.pipe( - mergeMap((result) => (isInvalidPackError(result.error) ? [result.error] : [])) - ), - - otherError$: pack$.pipe( - mergeMap((result) => (isUnhandledError(result.error) ? [result.error] : [])) - ), - - // { spec, message } objects produced when transforming deprecated - // settings for a plugin spec - deprecation$: extendConfig$.pipe(mergeMap((result) => result.deprecations)), - - // the config service we extended with all of the plugin specs, - // only emitted once it is fully extended by all - extendedConfig$: extendConfig$.pipe( - mergeMap((result) => result.config), - filter(Boolean), - last() - ), - - // all enabled PluginSpec objects - spec$: extendConfig$.pipe(mergeMap((result) => result.enabledSpecs)), - - // all disabled PluginSpec objects - disabledSpec$: extendConfig$.pipe(mergeMap((result) => result.disabledSpecs)), - - // all PluginSpec objects that were disabled because their version was incompatible - invalidVersionSpec$: extendConfig$.pipe(mergeMap((result) => result.invalidVersionSpecs)), - }; -} - -function isUnhandledError(error) { - return error != null && !isInvalidDirectoryError(error) && !isInvalidPackError(error); -} diff --git a/src/legacy/plugin_discovery/index.js b/src/legacy/plugin_discovery/index.js deleted file mode 100644 index b60806f6cbc23..0000000000000 --- a/src/legacy/plugin_discovery/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { findPluginSpecs } from './find_plugin_specs'; -export { reduceExportSpecs } from './plugin_exports'; -export { PluginPack } from './plugin_pack'; diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js b/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js deleted file mode 100644 index 40f84f6f54b3b..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; - -import { Config } from '../../../server/config'; -import { PluginPack } from '../../plugin_pack'; -import { extendConfigService, disableConfigExtension } from '../extend_config_service'; -import * as SchemaNS from '../schema'; -import * as SettingsNS from '../settings'; - -describe('plugin discovery/extend config service', () => { - const sandbox = sinon.createSandbox(); - afterEach(() => sandbox.restore()); - - const pluginSpec = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'test', - version: 'kibana', - }, - provider: ({ Plugin }) => - new Plugin({ - configPrefix: 'foo.bar.baz', - - config: (Joi) => - Joi.object({ - enabled: Joi.boolean().default(true), - test: Joi.string().default('bonk'), - }).default(), - }), - }) - .getPluginSpecs() - .pop(); - - describe('extendConfigService()', () => { - it('calls getSettings, getSchema, and Config.extendSchema() correctly', async () => { - const rootSettings = { - foo: { - bar: { - enabled: false, - }, - }, - }; - const schema = { - validate: () => {}, - }; - const configPrefix = 'foo.bar'; - const config = { - extendSchema: sandbox.stub(), - }; - const pluginSpec = { - getConfigPrefix: sandbox.stub().returns(configPrefix), - }; - - const getSettings = sandbox.stub(SettingsNS, 'getSettings').returns(rootSettings.foo.bar); - - const getSchema = sandbox.stub(SchemaNS, 'getSchema').returns(schema); - - await extendConfigService(pluginSpec, config, rootSettings); - - sinon.assert.calledOnce(getSettings); - sinon.assert.calledWithExactly(getSettings, pluginSpec, rootSettings); - - sinon.assert.calledOnce(getSchema); - sinon.assert.calledWithExactly(getSchema, pluginSpec); - - sinon.assert.calledOnce(config.extendSchema); - sinon.assert.calledWithExactly( - config.extendSchema, - schema, - rootSettings.foo.bar, - configPrefix - ); - }); - - it('adds the schema for a plugin spec to its config prefix', async () => { - const config = Config.withDefaultSchema(); - expect(config.has('foo.bar.baz')).to.be(false); - await extendConfigService(pluginSpec, config); - expect(config.has('foo.bar.baz')).to.be(true); - }); - - it('initializes it with the default settings', async () => { - const config = Config.withDefaultSchema(); - await extendConfigService(pluginSpec, config); - expect(config.get('foo.bar.baz.enabled')).to.be(true); - expect(config.get('foo.bar.baz.test')).to.be('bonk'); - }); - - it('initializes it with values from root settings if defined', async () => { - const config = Config.withDefaultSchema(); - await extendConfigService(pluginSpec, config, { - foo: { - bar: { - baz: { - test: 'hello world', - }, - }, - }, - }); - - expect(config.get('foo.bar.baz.test')).to.be('hello world'); - }); - - it('throws if root settings are invalid', async () => { - const config = Config.withDefaultSchema(); - try { - await extendConfigService(pluginSpec, config, { - foo: { - bar: { - baz: { - test: { - 'not a string': true, - }, - }, - }, - }, - }); - throw new Error('Expected extendConfigService() to throw because of bad settings'); - } catch (error) { - expect(error.message).to.contain('"test" must be a string'); - } - }); - }); - - describe('disableConfigExtension()', () => { - it('removes added config', async () => { - const config = Config.withDefaultSchema(); - await extendConfigService(pluginSpec, config); - expect(config.has('foo.bar.baz.test')).to.be(true); - await disableConfigExtension(pluginSpec, config); - expect(config.has('foo.bar.baz.test')).to.be(false); - }); - - it('leaves {configPrefix}.enabled config', async () => { - const config = Config.withDefaultSchema(); - expect(config.has('foo.bar.baz.enabled')).to.be(false); - await extendConfigService(pluginSpec, config); - expect(config.get('foo.bar.baz.enabled')).to.be(true); - await disableConfigExtension(pluginSpec, config); - expect(config.get('foo.bar.baz.enabled')).to.be(false); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js b/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js deleted file mode 100644 index 78adb1e680e20..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { PluginPack } from '../../plugin_pack'; -import { getSchema, getStubSchema } from '../schema'; - -describe('plugin discovery/schema', () => { - function createPluginSpec(configProvider) { - return new PluginPack({ - path: '/dev/null', - pkg: { - name: 'test', - version: 'kibana', - }, - provider: ({ Plugin }) => - new Plugin({ - configPrefix: 'foo.bar.baz', - config: configProvider, - }), - }) - .getPluginSpecs() - .pop(); - } - - describe('getSchema()', () => { - it('calls the config provider and returns its return value', async () => { - const pluginSpec = createPluginSpec(() => 'foo'); - expect(await getSchema(pluginSpec)).to.be('foo'); - }); - - it('supports config provider that returns a promise', async () => { - const pluginSpec = createPluginSpec(() => Promise.resolve('foo')); - expect(await getSchema(pluginSpec)).to.be('foo'); - }); - - it('uses default schema when no config provider', async () => { - const schema = await getSchema(createPluginSpec()); - expect(schema).to.be.an('object'); - expect(schema).to.have.property('validate').a('function'); - expect(schema.validate({}).value).to.eql({ - enabled: true, - }); - }); - - it('uses default schema when config returns falsy value', async () => { - const schema = await getSchema(createPluginSpec(() => null)); - expect(schema).to.be.an('object'); - expect(schema).to.have.property('validate').a('function'); - expect(schema.validate({}).value).to.eql({ - enabled: true, - }); - }); - - it('uses default schema when config promise resolves to falsy value', async () => { - const schema = await getSchema(createPluginSpec(() => Promise.resolve(null))); - expect(schema).to.be.an('object'); - expect(schema).to.have.property('validate').a('function'); - expect(schema.validate({}).value).to.eql({ - enabled: true, - }); - }); - }); - - describe('getStubSchema()', () => { - it('returns schema with enabled: false', async () => { - const schema = await getStubSchema(); - expect(schema).to.be.an('object'); - expect(schema).to.have.property('validate').a('function'); - expect(schema.validate({}).value).to.eql({ - enabled: false, - }); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js b/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js deleted file mode 100644 index 750c5ee6c6f50..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { PluginPack } from '../../plugin_pack'; -import { getSettings } from '../settings'; - -describe('plugin_discovery/settings', () => { - const pluginSpec = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'test', - version: 'kibana', - }, - provider: ({ Plugin }) => - new Plugin({ - configPrefix: 'a.b.c', - }), - }) - .getPluginSpecs() - .pop(); - - describe('getSettings()', () => { - it('reads settings from config prefix', async () => { - const rootSettings = { - a: { - b: { - c: { - enabled: false, - }, - }, - }, - }; - - expect(await getSettings(pluginSpec, rootSettings)).to.eql({ - enabled: false, - }); - }); - - it('allows rootSettings to be undefined', async () => { - expect(await getSettings(pluginSpec)).to.eql(undefined); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_config/extend_config_service.js b/src/legacy/plugin_discovery/plugin_config/extend_config_service.js deleted file mode 100644 index a6d5d4ae5f990..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/extend_config_service.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { getSettings } from './settings'; -import { getSchema, getStubSchema } from './schema'; - -/** - * Extend a config service with the schema and settings for a - * plugin spec and optionally call logDeprecation with warning - * messages about deprecated settings that are used - * @param {PluginSpec} spec - * @param {Server.Config} config - * @param {Object} rootSettings - * @param {Function} [logDeprecation] - * @return {Promise} - */ -export async function extendConfigService(spec, config, rootSettings) { - const settings = await getSettings(spec, rootSettings); - const schema = await getSchema(spec); - config.extendSchema(schema, settings, spec.getConfigPrefix()); -} - -/** - * Disable the schema and settings applied to a config service for - * a plugin spec - * @param {PluginSpec} spec - * @param {Server.Config} config - * @return {undefined} - */ -export function disableConfigExtension(spec, config) { - const prefix = spec.getConfigPrefix(); - config.removeSchema(prefix); - config.extendSchema(getStubSchema(), { enabled: false }, prefix); -} diff --git a/src/legacy/plugin_discovery/plugin_config/index.js b/src/legacy/plugin_discovery/plugin_config/index.js deleted file mode 100644 index a27463bc9c7f5..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { extendConfigService, disableConfigExtension } from './extend_config_service'; diff --git a/src/legacy/plugin_discovery/plugin_config/schema.js b/src/legacy/plugin_discovery/plugin_config/schema.js deleted file mode 100644 index 14d10aa5568da..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/schema.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Joi from 'joi'; - -const STUB_CONFIG_SCHEMA = Joi.object() - .keys({ - enabled: Joi.valid(false).default(false), - }) - .default(); - -const DEFAULT_CONFIG_SCHEMA = Joi.object() - .keys({ - enabled: Joi.boolean().default(true), - }) - .default(); - -/** - * Get the config schema for a plugin spec - * @param {PluginSpec} spec - * @return {Promise} - */ -export async function getSchema(spec) { - const provider = spec.getConfigSchemaProvider(); - return (provider && (await provider(Joi))) || DEFAULT_CONFIG_SCHEMA; -} - -export function getStubSchema() { - return STUB_CONFIG_SCHEMA; -} diff --git a/src/legacy/plugin_discovery/plugin_config/settings.js b/src/legacy/plugin_discovery/plugin_config/settings.js deleted file mode 100644 index e6a4741d76eca..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/settings.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { get } from 'lodash'; - -/** - * Get the settings for a pluginSpec from the raw root settings while - * optionally calling logDeprecation() with warnings about deprecated - * settings that were used - * @param {PluginSpec} spec - * @param {Object} rootSettings - * @return {Promise} - */ -export async function getSettings(spec, rootSettings) { - const prefix = spec.getConfigPrefix(); - const rawSettings = get(rootSettings, prefix); - return rawSettings; -} diff --git a/src/legacy/plugin_discovery/plugin_exports/__tests__/reduce_export_specs.js b/src/legacy/plugin_discovery/plugin_exports/__tests__/reduce_export_specs.js deleted file mode 100644 index 3beaacc1a8293..0000000000000 --- a/src/legacy/plugin_discovery/plugin_exports/__tests__/reduce_export_specs.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { PluginPack } from '../../plugin_pack'; -import { reduceExportSpecs } from '../reduce_export_specs'; - -const PLUGIN = new PluginPack({ - path: __dirname, - pkg: { - name: 'foo', - version: 'kibana', - }, - provider: ({ Plugin }) => - new Plugin({ - uiExports: { - concatNames: { - name: 'export1', - }, - - concat: ['export2', 'export3'], - }, - }), -}); - -const REDUCERS = { - concatNames(acc, spec, type, pluginSpec) { - return { - names: [].concat(acc.names || [], `${pluginSpec.getId()}:${spec.name}`), - }; - }, - concat(acc, spec, type, pluginSpec) { - return { - names: [].concat(acc.names || [], `${pluginSpec.getId()}:${spec}`), - }; - }, -}; - -const PLUGIN_SPECS = PLUGIN.getPluginSpecs(); - -describe('reduceExportSpecs', () => { - it('combines ui exports from a list of plugin definitions', () => { - const exports = reduceExportSpecs(PLUGIN_SPECS, REDUCERS); - expect(exports).to.eql({ - names: ['foo:export1', 'foo:export2', 'foo:export3'], - }); - }); - - it('starts with the defaults', () => { - const exports = reduceExportSpecs(PLUGIN_SPECS, REDUCERS, { - names: ['default'], - }); - - expect(exports).to.eql({ - names: ['default', 'foo:export1', 'foo:export2', 'foo:export3'], - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_exports/index.js b/src/legacy/plugin_discovery/plugin_exports/index.js deleted file mode 100644 index 0e3511ea85dd4..0000000000000 --- a/src/legacy/plugin_discovery/plugin_exports/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { reduceExportSpecs } from './reduce_export_specs'; diff --git a/src/legacy/plugin_discovery/plugin_exports/reduce_export_specs.js b/src/legacy/plugin_discovery/plugin_exports/reduce_export_specs.js deleted file mode 100644 index a3adc3091085d..0000000000000 --- a/src/legacy/plugin_discovery/plugin_exports/reduce_export_specs.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Combine the exportSpecs from a list of pluginSpecs - * by calling the reducers for each export type - * @param {Array} pluginSpecs - * @param {Object} reducers - * @param {Object} [defaults={}] - * @return {Object} - */ -export function reduceExportSpecs(pluginSpecs, reducers, defaults = {}) { - return pluginSpecs.reduce((acc, pluginSpec) => { - const specsByType = pluginSpec.getExportSpecs() || {}; - const types = Object.keys(specsByType); - - return types.reduce((acc, type) => { - const reducer = reducers[type] || reducers.unknown; - - if (!reducer) { - throw new Error(`Unknown export type ${type}`); - } - - // convert specs to an array if not already one or - // ignore the spec if it is undefined - const specs = [].concat(specsByType[type] === undefined ? [] : specsByType[type]); - - return specs.reduce((acc, spec) => reducer(acc, spec, type, pluginSpec), acc); - }, acc); - }, defaults); -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js deleted file mode 100644 index b17bd69479ffa..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import * as Rx from 'rxjs'; -import { toArray } from 'rxjs/operators'; -import expect from '@kbn/expect'; - -import { createPack$ } from '../create_pack'; -import { PluginPack } from '../plugin_pack'; - -import { PLUGINS_DIR, assertInvalidPackError } from './utils'; - -describe('plugin discovery/create pack', () => { - it('creates PluginPack', async () => { - const packageJson$ = Rx.from([ - { - packageJson: { - directoryPath: resolve(PLUGINS_DIR, 'prebuilt'), - contents: { - name: 'prebuilt', - }, - }, - }, - ]); - const results = await createPack$(packageJson$).pipe(toArray()).toPromise(); - expect(results).to.have.length(1); - expect(results[0]).to.only.have.keys(['pack']); - const { pack } = results[0]; - expect(pack).to.be.a(PluginPack); - }); - - describe('errors thrown', () => { - async function checkError(path, check) { - const packageJson$ = Rx.from([ - { - packageJson: { - directoryPath: path, - }, - }, - ]); - - const results = await createPack$(packageJson$).pipe(toArray()).toPromise(); - expect(results).to.have.length(1); - expect(results[0]).to.only.have.keys(['error']); - const { error } = results[0]; - await check(error); - } - it('default export is an object', () => - checkError(resolve(PLUGINS_DIR, 'exports_object'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must export a function'); - })); - it('default export is an number', () => - checkError(resolve(PLUGINS_DIR, 'exports_number'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must export a function'); - })); - it('default export is an string', () => - checkError(resolve(PLUGINS_DIR, 'exports_string'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must export a function'); - })); - it('directory with code that fails when required', () => - checkError(resolve(PLUGINS_DIR, 'broken_code'), (error) => { - expect(error.message).to.contain("Cannot find module 'does-not-exist'"); - })); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken/package.json deleted file mode 100644 index f830e8b60c02d..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js deleted file mode 100644 index bdb26504d6b6e..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const brokenRequire = require('does-not-exist'); // eslint-disable-line - -module.exports = function (kibana) { - return new kibana.Plugin({ - id: 'foo', - }); -}; diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/index.js deleted file mode 100644 index f24fc54e38d9a..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default 1; diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js deleted file mode 100644 index e43a1dcedb372..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -module.exports = function (kibana) { - return new kibana.Plugin({ - id: 'foo', - }); -}; diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/index.js deleted file mode 100644 index edb1dd15673da..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -console.log('hello world'); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/index.js deleted file mode 100644 index 050ffdfbde9ea..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { myLib } from './my_lib'; diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/my_lib.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/my_lib.js deleted file mode 100644 index 94e511632d9a6..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/my_lib.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export function myLib() { - console.log('lib'); -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/index.js deleted file mode 100644 index 89744b2dd3fd9..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -exports.default = function (_ref) { - var Plugin = _ref.Plugin; - - return new Plugin({ - id: 'foo' - }); -}; diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/package.json deleted file mode 100644 index b1b74e0e76b12..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "prebuilt" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js deleted file mode 100644 index fa1033180954e..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { toArray } from 'rxjs/operators'; - -import expect from '@kbn/expect'; - -import { createPackageJsonAtPath$ } from '../package_json_at_path'; -import { PLUGINS_DIR, assertInvalidPackError, assertInvalidDirectoryError } from './utils'; - -describe('plugin discovery/plugin_pack', () => { - describe('createPackageJsonAtPath$()', () => { - it('returns an observable', () => { - expect(createPackageJsonAtPath$()).to.have.property('subscribe').a('function'); - }); - it('gets the default provider from prebuilt babel modules', async () => { - const results = await createPackageJsonAtPath$(resolve(PLUGINS_DIR, 'prebuilt')) - .pipe(toArray()) - .toPromise(); - expect(results).to.have.length(1); - expect(results[0]).to.only.have.keys(['packageJson']); - expect(results[0].packageJson).to.be.an(Object); - expect(results[0].packageJson.directoryPath).to.be(resolve(PLUGINS_DIR, 'prebuilt')); - expect(results[0].packageJson.contents).to.eql({ name: 'prebuilt' }); - }); - describe('errors emitted as { error } results', () => { - async function checkError(path, check) { - const results = await createPackageJsonAtPath$(path).pipe(toArray()).toPromise(); - expect(results).to.have.length(1); - expect(results[0]).to.only.have.keys(['error']); - const { error } = results[0]; - await check(error); - } - it('undefined path', () => - checkError(undefined, (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be a string'); - })); - it('relative path', () => - checkError('plugins/foo', (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be absolute'); - })); - it('./relative path', () => - checkError('./plugins/foo', (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be absolute'); - })); - it('non-existent path', () => - checkError(resolve(PLUGINS_DIR, 'baz'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must be a directory'); - })); - it('path to a file', () => - checkError(resolve(PLUGINS_DIR, 'index.js'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must be a directory'); - })); - it('directory without a package.json', () => - checkError(resolve(PLUGINS_DIR, 'lib'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must have a package.json file'); - })); - it('directory with an invalid package.json', () => - checkError(resolve(PLUGINS_DIR, 'broken'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must have a valid package.json file'); - })); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js deleted file mode 100644 index 37cb4cc064da7..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; - -import { toArray } from 'rxjs/operators'; -import expect from '@kbn/expect'; - -import { createPackageJsonsInDirectory$ } from '../package_jsons_in_directory'; - -import { PLUGINS_DIR, assertInvalidDirectoryError } from './utils'; - -describe('plugin discovery/packs in directory', () => { - describe('createPackageJsonsInDirectory$()', () => { - describe('errors emitted as { error } results', () => { - async function checkError(path, check) { - const results = await createPackageJsonsInDirectory$(path).pipe(toArray()).toPromise(); - expect(results).to.have.length(1); - expect(results[0]).to.only.have.keys('error'); - const { error } = results[0]; - await check(error); - } - - it('undefined path', () => - checkError(undefined, (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be a string'); - })); - it('relative path', () => - checkError('my/plugins', (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be absolute'); - })); - it('./relative path', () => - checkError('./my/pluginsd', (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be absolute'); - })); - it('non-existent path', () => - checkError(resolve(PLUGINS_DIR, 'notreal'), (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('no such file or directory'); - })); - it('path to a file', () => - checkError(resolve(PLUGINS_DIR, 'index.js'), (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('not a directory'); - })); - }); - - it('includes child errors for invalid packageJsons within a valid directory', async () => { - const results = await createPackageJsonsInDirectory$(PLUGINS_DIR).pipe(toArray()).toPromise(); - - const errors = results.map((result) => result.error).filter(Boolean); - - const packageJsons = results.map((result) => result.packageJson).filter(Boolean); - - packageJsons.forEach((pack) => expect(pack).to.be.an(Object)); - // there should be one result for each item in PLUGINS_DIR - expect(results).to.have.length(8); - // three of the fixtures are errors of some sort - expect(errors).to.have.length(2); - // six of them are valid - expect(packageJsons).to.have.length(6); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js deleted file mode 100644 index 769fcd74ce6fb..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { PluginPack } from '../plugin_pack'; -import { PluginSpec } from '../../plugin_spec'; - -describe('plugin discovery/plugin pack', () => { - describe('constructor', () => { - it('requires an object', () => { - expect(() => { - new PluginPack(); - }).to.throwError(); - }); - }); - describe('#getPkg()', () => { - it('returns the `pkg` constructor argument', () => { - const pkg = {}; - const pack = new PluginPack({ pkg }); - expect(pack.getPkg()).to.be(pkg); - }); - }); - describe('#getPath()', () => { - it('returns the `path` constructor argument', () => { - const path = {}; - const pack = new PluginPack({ path }); - expect(pack.getPath()).to.be(path); - }); - }); - describe('#getPluginSpecs()', () => { - it('calls the `provider` constructor argument with an api including a single sub class of PluginSpec', () => { - const provider = sinon.stub(); - const pack = new PluginPack({ provider }); - sinon.assert.notCalled(provider); - pack.getPluginSpecs(); - sinon.assert.calledOnce(provider); - sinon.assert.calledWithExactly(provider, { - Plugin: sinon.match((Class) => { - return Class.prototype instanceof PluginSpec; - }, 'Subclass of PluginSpec'), - }); - }); - - it('casts undefined return value to array', () => { - const pack = new PluginPack({ provider: () => undefined }); - expect(pack.getPluginSpecs()).to.eql([]); - }); - - it('casts single PluginSpec to an array', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { name: 'foo', version: 'kibana' }, - provider: ({ Plugin }) => new Plugin({}), - }); - - const specs = pack.getPluginSpecs(); - expect(specs).to.be.an('array'); - expect(specs).to.have.length(1); - expect(specs[0]).to.be.a(PluginSpec); - }); - - it('returns an array of PluginSpec', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { name: 'foo', version: 'kibana' }, - provider: ({ Plugin }) => [new Plugin({}), new Plugin({})], - }); - - const specs = pack.getPluginSpecs(); - expect(specs).to.be.an('array'); - expect(specs).to.have.length(2); - expect(specs[0]).to.be.a(PluginSpec); - expect(specs[1]).to.be.a(PluginSpec); - }); - - it('throws if non-undefined return value is not an instance of api.Plugin', () => { - let OtherPluginSpecClass; - const otherPack = new PluginPack({ - path: '/dev/null', - pkg: { name: 'foo', version: 'kibana' }, - provider: (api) => { - OtherPluginSpecClass = api.Plugin; - }, - }); - - // call getPluginSpecs() on other pack to get it's api.Plugin class - otherPack.getPluginSpecs(); - - const badPacks = [ - new PluginPack({ provider: () => false }), - new PluginPack({ provider: () => null }), - new PluginPack({ provider: () => 1 }), - new PluginPack({ provider: () => 'true' }), - new PluginPack({ provider: () => true }), - new PluginPack({ provider: () => new Date() }), - new PluginPack({ provider: () => /foo.*bar/ }), - new PluginPack({ provider: () => function () {} }), - new PluginPack({ provider: () => new OtherPluginSpecClass({}) }), - ]; - - for (const pack of badPacks) { - expect(() => pack.getPluginSpecs()).to.throwError((error) => { - expect(error.message).to.contain('unexpected plugin export'); - }); - } - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_pack/create_pack.js b/src/legacy/plugin_discovery/plugin_pack/create_pack.js deleted file mode 100644 index 189c2ea324103..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/create_pack.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { PluginPack } from './plugin_pack'; -import { map, catchError } from 'rxjs/operators'; -import { createInvalidPackError } from '../errors'; - -function createPack(packageJson) { - let provider = require(packageJson.directoryPath); // eslint-disable-line import/no-dynamic-require - if (provider.__esModule) { - provider = provider.default; - } - if (typeof provider !== 'function') { - throw createInvalidPackError(packageJson.directoryPath, 'must export a function'); - } - - return new PluginPack({ path: packageJson.directoryPath, pkg: packageJson.contents, provider }); -} - -export const createPack$ = (packageJson$) => - packageJson$.pipe( - map(({ error, packageJson }) => { - if (error) { - return { error }; - } - - if (!packageJson) { - throw new Error('packageJson is required to create the pack'); - } - - return { - pack: createPack(packageJson), - }; - }), - // createPack can throw errors, and we want them to be represented - // like the errors we consume from createPackageJsonAtPath/Directory - catchError((error) => [{ error }]) - ); diff --git a/src/legacy/plugin_discovery/plugin_pack/index.js b/src/legacy/plugin_discovery/plugin_pack/index.js deleted file mode 100644 index 69e55baee660b..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { createPack$ } from './create_pack'; -export { createPackageJsonAtPath$ } from './package_json_at_path'; -export { createPackageJsonsInDirectory$ } from './package_jsons_in_directory'; -export { PluginPack } from './plugin_pack'; diff --git a/src/legacy/plugin_discovery/plugin_pack/lib/fs.js b/src/legacy/plugin_discovery/plugin_pack/lib/fs.js deleted file mode 100644 index 2b531e314df52..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/lib/fs.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { stat, readdir } from 'fs'; -import { resolve, isAbsolute } from 'path'; - -import { fromNode as fcb } from 'bluebird'; -import * as Rx from 'rxjs'; -import { catchError, mergeAll, filter, map, mergeMap } from 'rxjs/operators'; - -import { createInvalidDirectoryError } from '../../errors'; - -function assertAbsolutePath(path) { - if (typeof path !== 'string') { - throw createInvalidDirectoryError(new TypeError('path must be a string'), path); - } - - if (!isAbsolute(path)) { - throw createInvalidDirectoryError(new TypeError('path must be absolute'), path); - } -} - -async function statTest(path, test) { - try { - const stats = await fcb((cb) => stat(path, cb)); - return Boolean(test(stats)); - } catch (error) { - if (error.code !== 'ENOENT') { - throw error; - } - } - return false; -} - -/** - * Determine if a path currently points to a directory - * @param {String} path - * @return {Promise} - */ -export async function isDirectory(path) { - assertAbsolutePath(path); - return await statTest(path, (stat) => stat.isDirectory()); -} - -/** - * Get absolute paths for child directories within a path - * @param {string} path - * @return {Promise>} - */ -export const createChildDirectory$ = (path) => - Rx.defer(() => { - assertAbsolutePath(path); - return fcb((cb) => readdir(path, cb)); - }).pipe( - catchError((error) => { - throw createInvalidDirectoryError(error, path); - }), - mergeAll(), - filter((name) => !name.startsWith('.')), - map((name) => resolve(path, name)), - mergeMap(async (absolute) => { - if (await isDirectory(absolute)) { - return [absolute]; - } else { - return []; - } - }), - mergeAll() - ); diff --git a/src/legacy/plugin_discovery/plugin_pack/lib/index.js b/src/legacy/plugin_discovery/plugin_pack/lib/index.js deleted file mode 100644 index 491deeda27516..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/lib/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { isDirectory, createChildDirectory$ } from './fs'; diff --git a/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js b/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js deleted file mode 100644 index 18629ef3ea802..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { readFileSync } from 'fs'; -import * as Rx from 'rxjs'; -import { map, mergeMap, catchError } from 'rxjs/operators'; -import { resolve } from 'path'; -import { createInvalidPackError } from '../errors'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { isNewPlatformPlugin } from '../../../core/server/plugins'; - -import { isDirectory } from './lib'; - -async function createPackageJsonAtPath(path) { - if (!(await isDirectory(path))) { - throw createInvalidPackError(path, 'must be a directory'); - } - - let str; - try { - str = readFileSync(resolve(path, 'package.json')); - } catch (err) { - throw createInvalidPackError(path, 'must have a package.json file'); - } - - let pkg; - try { - pkg = JSON.parse(str); - } catch (err) { - throw createInvalidPackError(path, 'must have a valid package.json file'); - } - - return { - directoryPath: path, - contents: pkg, - }; -} - -export const createPackageJsonAtPath$ = (path) => - // If plugin directory contains manifest file, we should skip it since it - // should have been handled by the core plugin system already. - Rx.defer(() => isNewPlatformPlugin(path)).pipe( - mergeMap((isNewPlatformPlugin) => (isNewPlatformPlugin ? [] : createPackageJsonAtPath(path))), - map((packageJson) => ({ packageJson })), - catchError((error) => [{ error }]) - ); diff --git a/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js b/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js deleted file mode 100644 index 5f0977f4829b8..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { mergeMap, catchError } from 'rxjs/operators'; -import { isInvalidDirectoryError } from '../errors'; - -import { createChildDirectory$ } from './lib'; -import { createPackageJsonAtPath$ } from './package_json_at_path'; - -/** - * Finds the plugins within a directory. Results are - * an array of objects with either `pack` or `error` - * keys. - * - * - `{ error }` results are provided when the path is not - * a directory, or one of the child directories is not a - * valid plugin pack. - * - `{ pack }` results are for discovered plugins defs - * - * @param {String} path - * @return {Array<{pack}|{error}>} - */ -export const createPackageJsonsInDirectory$ = (path) => - createChildDirectory$(path).pipe( - mergeMap(createPackageJsonAtPath$), - catchError((error) => { - // this error is produced by createChildDirectory$() when the path - // is invalid, we return them as an error result similar to how - // createPackAtPath$ works when it finds invalid packs in a directory - if (isInvalidDirectoryError(error)) { - return [{ error }]; - } - - throw error; - }) - ); diff --git a/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js b/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js deleted file mode 100644 index 1baf3d104ca84..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { inspect } from 'util'; - -import { PluginSpec } from '../plugin_spec'; - -export class PluginPack { - constructor({ path, pkg, provider }) { - this._path = path; - this._pkg = pkg; - this._provider = provider; - } - - /** - * Get the contents of this plugin pack's package.json file - * @return {Object} - */ - getPkg() { - return this._pkg; - } - - /** - * Get the absolute path to this plugin pack on disk - * @return {String} - */ - getPath() { - return this._path; - } - - /** - * Invoke the plugin pack's provider to get the list - * of specs defined in this plugin. - * @return {Array} - */ - getPluginSpecs() { - const pack = this; - const api = { - Plugin: class ScopedPluginSpec extends PluginSpec { - constructor(options) { - super(pack, options); - } - }, - }; - - const result = this._provider(api); - const specs = [].concat(result === undefined ? [] : result); - - // verify that all specs are instances of passed "Plugin" class - specs.forEach((spec) => { - if (!(spec instanceof api.Plugin)) { - throw new TypeError('unexpected plugin export ' + inspect(spec)); - } - }); - - return specs; - } -} diff --git a/src/legacy/plugin_discovery/plugin_spec/__tests__/is_version_compatible.js b/src/legacy/plugin_discovery/plugin_spec/__tests__/is_version_compatible.js deleted file mode 100644 index 897184496af37..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/__tests__/is_version_compatible.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { isVersionCompatible } from '../is_version_compatible'; - -describe('plugin discovery/plugin spec', () => { - describe('isVersionCompatible()', () => { - const tests = [ - ['kibana', '6.0.0', true], - ['kibana', '6.0.0-rc1', true], - ['6.0.0-rc1', '6.0.0', true], - ['6.0.0', '6.0.0-rc1', true], - ['6.0.0-rc2', '6.0.0-rc1', true], - ['6.0.0-rc2', '6.0.0-rc3', true], - ['foo', 'bar', false], - ['6.0.0', '5.1.4', false], - ['5.1.4', '6.0.0', false], - ['5.1.4-SNAPSHOT', '6.0.0-rc2-SNAPSHOT', false], - ['5.1.4', '6.0.0-rc2-SNAPSHOT', false], - ['5.1.4-SNAPSHOT', '6.0.0', false], - ['5.1.4-SNAPSHOT', '6.0.0-rc2', false], - ]; - - for (const [plugin, kibana, shouldPass] of tests) { - it(`${shouldPass ? 'should' : `shouldn't`} allow plugin: ${plugin} kibana: ${kibana}`, () => { - expect(isVersionCompatible(plugin, kibana)).to.be(shouldPass); - }); - } - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js b/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js deleted file mode 100644 index 02675f0bd60f8..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { PluginPack } from '../../plugin_pack'; -import { PluginSpec } from '../plugin_spec'; -import * as IsVersionCompatibleNS from '../is_version_compatible'; - -const fooPack = new PluginPack({ - path: '/dev/null', - pkg: { name: 'foo', version: 'kibana' }, -}); - -describe('plugin discovery/plugin spec', () => { - describe('PluginSpec', () => { - describe('validation', () => { - it('throws if missing spec.id AND Pack has no name', () => { - const pack = new PluginPack({ pkg: {} }); - expect(() => new PluginSpec(pack, {})).to.throwError((error) => { - expect(error.message).to.contain('Unable to determine plugin id'); - }); - }); - - it('throws if missing spec.kibanaVersion AND Pack has no version', () => { - const pack = new PluginPack({ pkg: { name: 'foo' } }); - expect(() => new PluginSpec(pack, {})).to.throwError((error) => { - expect(error.message).to.contain('Unable to determine plugin version'); - }); - }); - - it('throws if spec.require is defined, but not an array', () => { - function assert(require) { - expect(() => new PluginSpec(fooPack, { require })).to.throwError((error) => { - expect(error.message).to.contain('"plugin.require" must be an array of plugin ids'); - }); - } - - assert(null); - assert(''); - assert('kibana'); - assert(1); - assert(0); - assert(/a.*b/); - }); - - it('throws if spec.publicDir is truthy and not a string', () => { - function assert(publicDir) { - expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => { - expect(error.message).to.contain( - `The "path" argument must be of type string. Received type ${typeof publicDir}` - ); - }); - } - - assert(1); - assert(function () {}); - assert([]); - assert(/a.*b/); - }); - - it('throws if spec.publicDir is not an absolute path', () => { - function assert(publicDir) { - expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => { - expect(error.message).to.contain('plugin.publicDir must be an absolute path'); - }); - } - - assert('relative/path'); - assert('./relative/path'); - }); - - it('throws if spec.publicDir basename is not `public`', () => { - function assert(publicDir) { - expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => { - expect(error.message).to.contain('must end with a "public" directory'); - }); - } - - assert('/www'); - assert('/www/'); - assert('/www/public/my_plugin'); - assert('/www/public/my_plugin/'); - }); - }); - - describe('#getPack()', () => { - it('returns the pack', () => { - const spec = new PluginSpec(fooPack, {}); - expect(spec.getPack()).to.be(fooPack); - }); - }); - - describe('#getPkg()', () => { - it('returns the pkg from the pack', () => { - const spec = new PluginSpec(fooPack, {}); - expect(spec.getPkg()).to.be(fooPack.getPkg()); - }); - }); - - describe('#getPath()', () => { - it('returns the path from the pack', () => { - const spec = new PluginSpec(fooPack, {}); - expect(spec.getPath()).to.be(fooPack.getPath()); - }); - }); - - describe('#getId()', () => { - it('uses spec.id', () => { - const spec = new PluginSpec(fooPack, { - id: 'bar', - }); - - expect(spec.getId()).to.be('bar'); - }); - - it('defaults to pack.pkg.name', () => { - const spec = new PluginSpec(fooPack, {}); - - expect(spec.getId()).to.be('foo'); - }); - }); - - describe('#getVersion()', () => { - it('uses spec.version', () => { - const spec = new PluginSpec(fooPack, { - version: 'bar', - }); - - expect(spec.getVersion()).to.be('bar'); - }); - - it('defaults to pack.pkg.version', () => { - const spec = new PluginSpec(fooPack, {}); - - expect(spec.getVersion()).to.be('kibana'); - }); - }); - - describe('#isEnabled()', () => { - describe('spec.isEnabled is not defined', () => { - function setup(configPrefix, configGetImpl) { - const spec = new PluginSpec(fooPack, { configPrefix }); - const config = { - get: sinon.spy(configGetImpl), - has: sinon.stub(), - }; - - return { spec, config }; - } - - it('throws if not passed a config service', () => { - const { spec } = setup('a.b.c', () => true); - - expect(() => spec.isEnabled()).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - expect(() => spec.isEnabled(null)).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - expect(() => spec.isEnabled({ get: () => {} })).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - }); - - it('returns true when config.get([...configPrefix, "enabled"]) returns true', () => { - const { spec, config } = setup('d.e.f', () => true); - - expect(spec.isEnabled(config)).to.be(true); - sinon.assert.calledOnce(config.get); - sinon.assert.calledWithExactly(config.get, ['d', 'e', 'f', 'enabled']); - }); - - it('returns false when config.get([...configPrefix, "enabled"]) returns false', () => { - const { spec, config } = setup('g.h.i', () => false); - - expect(spec.isEnabled(config)).to.be(false); - sinon.assert.calledOnce(config.get); - sinon.assert.calledWithExactly(config.get, ['g', 'h', 'i', 'enabled']); - }); - }); - - describe('spec.isEnabled is defined', () => { - function setup(isEnabledImpl) { - const isEnabled = sinon.spy(isEnabledImpl); - const spec = new PluginSpec(fooPack, { isEnabled }); - const config = { - get: sinon.stub(), - has: sinon.stub(), - }; - - return { isEnabled, spec, config }; - } - - it('throws if not passed a config service', () => { - const { spec } = setup(() => true); - - expect(() => spec.isEnabled()).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - expect(() => spec.isEnabled(null)).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - expect(() => spec.isEnabled({ get: () => {} })).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - }); - - it('does not check config if spec.isEnabled returns true', () => { - const { spec, isEnabled, config } = setup(() => true); - - expect(spec.isEnabled(config)).to.be(true); - sinon.assert.calledOnce(isEnabled); - sinon.assert.notCalled(config.get); - }); - - it('does not check config if spec.isEnabled returns false', () => { - const { spec, isEnabled, config } = setup(() => false); - - expect(spec.isEnabled(config)).to.be(false); - sinon.assert.calledOnce(isEnabled); - sinon.assert.notCalled(config.get); - }); - }); - }); - - describe('#getExpectedKibanaVersion()', () => { - describe('has: spec.kibanaVersion,pkg.kibana.version,spec.version,pkg.version', () => { - it('uses spec.kibanaVersion', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'expkv', - version: '1.0.0', - kibana: { - version: '6.0.0', - }, - }, - }); - - const spec = new PluginSpec(pack, { - version: '2.0.0', - kibanaVersion: '5.0.0', - }); - - expect(spec.getExpectedKibanaVersion()).to.be('5.0.0'); - }); - }); - describe('missing: spec.kibanaVersion, has: pkg.kibana.version,spec.version,pkg.version', () => { - it('uses pkg.kibana.version', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'expkv', - version: '1.0.0', - kibana: { - version: '6.0.0', - }, - }, - }); - - const spec = new PluginSpec(pack, { - version: '2.0.0', - }); - - expect(spec.getExpectedKibanaVersion()).to.be('6.0.0'); - }); - }); - describe('missing: spec.kibanaVersion,pkg.kibana.version, has: spec.version,pkg.version', () => { - it('uses spec.version', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'expkv', - version: '1.0.0', - }, - }); - - const spec = new PluginSpec(pack, { - version: '2.0.0', - }); - - expect(spec.getExpectedKibanaVersion()).to.be('2.0.0'); - }); - }); - describe('missing: spec.kibanaVersion,pkg.kibana.version,spec.version, has: pkg.version', () => { - it('uses pkg.version', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'expkv', - version: '1.0.0', - }, - }); - - const spec = new PluginSpec(pack, {}); - - expect(spec.getExpectedKibanaVersion()).to.be('1.0.0'); - }); - }); - }); - - describe('#isVersionCompatible()', () => { - it('passes this.getExpectedKibanaVersion() and arg to isVersionCompatible(), returns its result', () => { - const spec = new PluginSpec(fooPack, { version: '1.0.0' }); - sinon.stub(spec, 'getExpectedKibanaVersion').returns('foo'); - const isVersionCompatible = sinon - .stub(IsVersionCompatibleNS, 'isVersionCompatible') - .returns('bar'); - expect(spec.isVersionCompatible('baz')).to.be('bar'); - - sinon.assert.calledOnce(spec.getExpectedKibanaVersion); - sinon.assert.calledWithExactly(spec.getExpectedKibanaVersion); - - sinon.assert.calledOnce(isVersionCompatible); - sinon.assert.calledWithExactly(isVersionCompatible, 'foo', 'baz'); - }); - }); - - describe('#getRequiredPluginIds()', () => { - it('returns spec.require', () => { - const spec = new PluginSpec(fooPack, { require: [1, 2, 3] }); - expect(spec.getRequiredPluginIds()).to.eql([1, 2, 3]); - }); - }); - - describe('#getPublicDir()', () => { - describe('spec.publicDir === false', () => { - it('returns null', () => { - const spec = new PluginSpec(fooPack, { publicDir: false }); - expect(spec.getPublicDir()).to.be(null); - }); - }); - - describe('spec.publicDir is falsy', () => { - it('returns public child of pack path', () => { - function assert(publicDir) { - const spec = new PluginSpec(fooPack, { publicDir }); - expect(spec.getPublicDir()).to.be(resolve('/dev/null/public')); - } - - assert(0); - assert(''); - assert(null); - assert(undefined); - assert(NaN); - }); - }); - - describe('spec.publicDir is an absolute path', () => { - it('returns the path', () => { - const spec = new PluginSpec(fooPack, { - publicDir: '/var/www/public', - }); - - expect(spec.getPublicDir()).to.be('/var/www/public'); - }); - }); - - // NOTE: see constructor tests for other truthy-tests that throw in constructor - }); - - describe('#getExportSpecs()', () => { - it('returns spec.uiExports', () => { - const spec = new PluginSpec(fooPack, { - uiExports: 'foo', - }); - - expect(spec.getExportSpecs()).to.be('foo'); - }); - }); - - describe('#getPreInitHandler()', () => { - it('returns spec.preInit', () => { - const spec = new PluginSpec(fooPack, { - preInit: 'foo', - }); - - expect(spec.getPreInitHandler()).to.be('foo'); - }); - }); - - describe('#getInitHandler()', () => { - it('returns spec.init', () => { - const spec = new PluginSpec(fooPack, { - init: 'foo', - }); - - expect(spec.getInitHandler()).to.be('foo'); - }); - }); - - describe('#getConfigPrefix()', () => { - describe('spec.configPrefix is truthy', () => { - it('returns spec.configPrefix', () => { - const spec = new PluginSpec(fooPack, { - configPrefix: 'foo.bar.baz', - }); - - expect(spec.getConfigPrefix()).to.be('foo.bar.baz'); - }); - }); - describe('spec.configPrefix is falsy', () => { - it('returns spec.getId()', () => { - function assert(configPrefix) { - const spec = new PluginSpec(fooPack, { configPrefix }); - sinon.stub(spec, 'getId').returns('foo'); - expect(spec.getConfigPrefix()).to.be('foo'); - sinon.assert.calledOnce(spec.getId); - } - - assert(false); - assert(null); - assert(undefined); - assert(''); - assert(0); - }); - }); - }); - - describe('#getConfigSchemaProvider()', () => { - it('returns spec.config', () => { - const spec = new PluginSpec(fooPack, { - config: 'foo', - }); - - expect(spec.getConfigSchemaProvider()).to.be('foo'); - }); - }); - - describe('#readConfigValue()', () => { - const spec = new PluginSpec(fooPack, { - configPrefix: 'foo.bar', - }); - - const config = { - get: sinon.stub(), - }; - - afterEach(() => config.get.resetHistory()); - - describe('key = "foo"', () => { - it('passes key as own array item', () => { - spec.readConfigValue(config, 'foo'); - sinon.assert.calledOnce(config.get); - sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo']); - }); - }); - - describe('key = "foo.bar"', () => { - it('passes key as two array items', () => { - spec.readConfigValue(config, 'foo.bar'); - sinon.assert.calledOnce(config.get); - sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo', 'bar']); - }); - }); - - describe('key = ["foo", "bar"]', () => { - it('merged keys into array', () => { - spec.readConfigValue(config, ['foo', 'bar']); - sinon.assert.calledOnce(config.get); - sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo', 'bar']); - }); - }); - }); - - describe('#getDeprecationsProvider()', () => { - it('returns spec.deprecations', () => { - const spec = new PluginSpec(fooPack, { - deprecations: 'foo', - }); - - expect(spec.getDeprecationsProvider()).to.be('foo'); - }); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_spec/index.js b/src/legacy/plugin_discovery/plugin_spec/index.js deleted file mode 100644 index 671d311b152e2..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { PluginSpec } from './plugin_spec'; diff --git a/src/legacy/plugin_discovery/plugin_spec/is_version_compatible.js b/src/legacy/plugin_discovery/plugin_spec/is_version_compatible.js deleted file mode 100644 index 6822c168f368d..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/is_version_compatible.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { cleanVersion, versionSatisfies } from '../../utils/version'; - -export function isVersionCompatible(version, compatibleWith) { - // the special "kibana" version can be used to always be compatible, - // but is intentionally not supported by the plugin installer - if (version === 'kibana') { - return true; - } - - return versionSatisfies(cleanVersion(version), cleanVersion(compatibleWith)); -} diff --git a/src/legacy/plugin_discovery/plugin_spec/plugin_spec.js b/src/legacy/plugin_discovery/plugin_spec/plugin_spec.js deleted file mode 100644 index db1ec425f2ce5..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/plugin_spec.js +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve, basename, isAbsolute as isAbsolutePath } from 'path'; - -import { get, toPath } from 'lodash'; - -import { createInvalidPluginError } from '../errors'; -import { isVersionCompatible } from './is_version_compatible'; - -export class PluginSpec { - /** - * @param {PluginPack} pack The plugin pack that produced this spec - * @param {Object} opts the options for this plugin - * @param {String} [opts.id=pkg.name] the id for this plugin. - * @param {Object} [opts.uiExports] a mapping of UiExport types to - * UI modules or metadata about the UI module - * @param {Array} [opts.require] the other plugins that this plugin - * requires. These plugins must exist and be enabled for this plugin - * to function. The require'd plugins will also be initialized first, - * in order to make sure that dependencies provided by these plugins - * are available - * @param {String} [opts.version=pkg.version] the version of this plugin - * @param {Function} [opts.init] A function that will be called to initialize - * this plugin at the appropriate time. - * @param {Function} [opts.configPrefix=this.id] The prefix to use for - * configuration values in the main configuration service - * @param {Function} [opts.config] A function that produces a configuration - * schema using Joi, which is passed as its first argument. - * @param {String|False} [opts.publicDir=path + '/public'] the public - * directory for this plugin. The final directory must have the name "public", - * though it can be located somewhere besides the root of the plugin. Set - * this to false to disable exposure of a public directory - */ - constructor(pack, options) { - const { - id, - require, - version, - kibanaVersion, - uiExports, - uiCapabilities, - publicDir, - configPrefix, - config, - deprecations, - preInit, - init, - postInit, - isEnabled, - } = options; - - this._id = id; - this._pack = pack; - this._version = version; - this._kibanaVersion = kibanaVersion; - this._require = require; - - this._publicDir = publicDir; - this._uiExports = uiExports; - this._uiCapabilities = uiCapabilities; - - this._configPrefix = configPrefix; - this._configSchemaProvider = config; - this._configDeprecationsProvider = deprecations; - - this._isEnabled = isEnabled; - this._preInit = preInit; - this._init = init; - this._postInit = postInit; - - if (!this.getId()) { - throw createInvalidPluginError(this, 'Unable to determine plugin id'); - } - - if (!this.getVersion()) { - throw createInvalidPluginError(this, 'Unable to determine plugin version'); - } - - if (this.getRequiredPluginIds() !== undefined && !Array.isArray(this.getRequiredPluginIds())) { - throw createInvalidPluginError(this, '"plugin.require" must be an array of plugin ids'); - } - - if (this._publicDir) { - if (!isAbsolutePath(this._publicDir)) { - throw createInvalidPluginError(this, 'plugin.publicDir must be an absolute path'); - } - if (basename(this._publicDir) !== 'public') { - throw createInvalidPluginError( - this, - `publicDir for plugin ${this.getId()} must end with a "public" directory.` - ); - } - } - } - - getPack() { - return this._pack; - } - - getPkg() { - return this._pack.getPkg(); - } - - getPath() { - return this._pack.getPath(); - } - - getId() { - return this._id || this.getPkg().name; - } - - getVersion() { - return this._version || this.getPkg().version; - } - - isEnabled(config) { - if (!config || typeof config.get !== 'function' || typeof config.has !== 'function') { - throw new TypeError('PluginSpec#isEnabled() must be called with a config service'); - } - - if (this._isEnabled) { - return this._isEnabled(config); - } - - return Boolean(this.readConfigValue(config, 'enabled')); - } - - getExpectedKibanaVersion() { - // Plugins must specify their version, and by default that version should match - // the version of kibana down to the patch level. If these two versions need - // to diverge, they can specify a kibana.version in the package to indicate the - // version of kibana the plugin is intended to work with. - return ( - this._kibanaVersion || get(this.getPack().getPkg(), 'kibana.version') || this.getVersion() - ); - } - - isVersionCompatible(actualKibanaVersion) { - return isVersionCompatible(this.getExpectedKibanaVersion(), actualKibanaVersion); - } - - getRequiredPluginIds() { - return this._require; - } - - getPublicDir() { - if (this._publicDir === false) { - return null; - } - - if (!this._publicDir) { - return resolve(this.getPack().getPath(), 'public'); - } - - return this._publicDir; - } - - getExportSpecs() { - return this._uiExports; - } - - getUiCapabilitiesProvider() { - return this._uiCapabilities; - } - - getPreInitHandler() { - return this._preInit; - } - - getInitHandler() { - return this._init; - } - - getPostInitHandler() { - return this._postInit; - } - - getConfigPrefix() { - return this._configPrefix || this.getId(); - } - - getConfigSchemaProvider() { - return this._configSchemaProvider; - } - - readConfigValue(config, key) { - return config.get([...toPath(this.getConfigPrefix()), ...toPath(key)]); - } - - getDeprecationsProvider() { - return this._configDeprecationsProvider; - } -} diff --git a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts b/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts deleted file mode 100644 index e1ed2f57375a4..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { Server } from '../../server/kbn_server'; -import { Capabilities } from '../../../core/server'; - -export type InitPluginFunction = (server: Server) => void; -export interface UiExports { - injectDefaultVars?: (server: Server) => { [key: string]: any }; -} - -export interface PluginSpecOptions { - id: string; - require?: string[]; - publicDir?: string; - uiExports?: UiExports; - uiCapabilities?: Capabilities; - init?: InitPluginFunction; - config?: any; -} diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts deleted file mode 100644 index 700ca6fa68c95..0000000000000 --- a/src/legacy/plugin_discovery/types.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Server } from '../server/kbn_server'; -import { Capabilities } from '../../core/server'; -import { AppCategory } from '../../core/types'; - -/** - * Usage - * - * ``` - * const apmOss: LegacyPlugin = (kibana) => { - * return new kibana.Plugin({ - * id: 'apm_oss', - * // ... - * }); - * }; - * ``` - */ -export type LegacyPluginInitializer = (kibana: LegacyPluginApi) => ArrayOrItem; - -export type ArrayOrItem = T | T[]; - -export interface LegacyPluginApi { - Plugin: new (options: Partial) => LegacyPluginSpec; -} - -export interface LegacyPluginOptions { - id: string; - require: string[]; - version: string; - kibanaVersion: 'kibana'; - uiExports: Partial<{ - app: Partial<{ - title: string; - category?: AppCategory; - description: string; - main: string; - icon: string; - euiIconType: string; - order: number; - listed: boolean; - }>; - apps: any; - hacks: string[]; - visualize: string[]; - devTools: string[]; - injectDefaultVars: (server: Server) => Record; - home: string[]; - mappings: any; - migrations: any; - visTypes: string[]; - embeddableActions?: string[]; - embeddableFactories?: string[]; - uiSettingDefaults?: Record; - interpreter: string | string[]; - }>; - uiCapabilities?: Capabilities; - publicDir: any; - configPrefix: any; - config: any; - deprecations: any; - preInit: any; - init: InitPluginFunction; - postInit: any; - isEnabled: boolean; -} - -export type InitPluginFunction = (server: Server) => void; - -export interface LegacyPluginSpec { - getPack(): any; - getPkg(): any; - getPath(): string; - getId(): string; - getVersion(): string; - isEnabled(config: any): boolean; - getExpectedKibanaVersion(): string; - isVersionCompatible(actualKibanaVersion: any): boolean; - getRequiredPluginIds(): string[]; - getPublicDir(): string | null; - getExportSpecs(): any; - getUiCapabilitiesProvider(): any; - getPreInitHandler(): any; - getInitHandler(): any; - getPostInitHandler(): any; - getConfigPrefix(): string; - getConfigSchemaProvider(): any; - readConfigValue(config: any, key: string): any; - getDeprecationsProvider(): any; -} diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index f8736fb30f90e..a94766ef06926 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -131,6 +131,7 @@ export default () => }), }).default(), + // still used by the legacy i18n mixin plugins: Joi.object({ paths: Joi.array().items(Joi.string()).default([]), scanDirs: Joi.array().items(Joi.string()).default([]), @@ -146,71 +147,8 @@ export default () => status: Joi.object({ allowAnonymous: Joi.boolean().default(false), }).default(), - map: Joi.object({ - includeElasticMapsService: Joi.boolean().default(true), - proxyElasticMapsServiceInMaps: Joi.boolean().default(false), - tilemap: Joi.object({ - url: Joi.string(), - options: Joi.object({ - attribution: Joi.string(), - minZoom: Joi.number().min(0, 'Must be 0 or higher').default(0), - maxZoom: Joi.number().default(10), - tileSize: Joi.number(), - subdomains: Joi.array().items(Joi.string()).single(), - errorTileUrl: Joi.string().uri(), - tms: Joi.boolean(), - reuseTiles: Joi.boolean(), - bounds: Joi.array().items(Joi.array().items(Joi.number()).min(2).required()).min(2), - default: Joi.boolean(), - }).default({ - default: true, - }), - }).default(), - regionmap: Joi.object({ - includeElasticMapsService: Joi.boolean().default(true), - layers: Joi.array() - .items( - Joi.object({ - url: Joi.string(), - format: Joi.object({ - type: Joi.string().default('geojson'), - }).default({ - type: 'geojson', - }), - meta: Joi.object({ - feature_collection_path: Joi.string().default('data'), - }).default({ - feature_collection_path: 'data', - }), - attribution: Joi.string(), - name: Joi.string(), - fields: Joi.array().items( - Joi.object({ - name: Joi.string(), - description: Joi.string(), - }) - ), - }) - ) - .default([]), - }).default(), - manifestServiceUrl: Joi.string().default('').allow(''), - emsFileApiUrl: Joi.string().default('https://vector.maps.elastic.co'), - emsTileApiUrl: Joi.string().default('https://tiles.maps.elastic.co'), - emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v7.9'), - emsFontLibraryUrl: Joi.string().default( - 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf' - ), - emsTileLayerId: Joi.object({ - bright: Joi.string().default('road_map'), - desaturated: Joi.string().default('road_map_desaturated'), - dark: Joi.string().default('dark_map'), - }).default({ - bright: 'road_map', - desaturated: 'road_map_desaturated', - dark: 'dark_map', - }), - }).default(), + + map: HANDLED_IN_NEW_PLATFORM, i18n: Joi.object({ locale: Joi.string().default('en'), diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 3cfda0e0696bb..1718a9a8f55da 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -26,11 +26,10 @@ import { LoggerFactory, PackageInfo, LegacyServiceSetupDeps, - LegacyServiceDiscoverPlugins, } from '../../core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { LegacyConfig, ILegacyInternals } from '../../core/server/legacy'; +import { LegacyConfig } from '../../core/server/legacy'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { UiPlugins } from '../../core/server/plugins'; @@ -58,9 +57,7 @@ export interface PluginsSetup { export interface KibanaCore { __internals: { - elasticsearch: LegacyServiceSetupDeps['core']['elasticsearch']; hapiServer: LegacyServiceSetupDeps['core']['http']['server']; - legacy: ILegacyInternals; rendering: LegacyServiceSetupDeps['core']['rendering']; uiPlugins: UiPlugins; }; @@ -90,31 +87,18 @@ export interface NewPlatform { stop: null; } -export type LegacyPlugins = Pick< - LegacyServiceDiscoverPlugins, - 'pluginSpecs' | 'disabledPluginSpecs' | 'uiExports' ->; - // eslint-disable-next-line import/no-default-export export default class KbnServer { public readonly newPlatform: NewPlatform; public server: Server; public inject: Server['inject']; - public pluginSpecs: any[]; - public uiBundles: any; - constructor( - settings: Record, - config: KibanaConfig, - core: KibanaCore, - legacyPlugins: LegacyPlugins - ); + constructor(settings: Record, config: KibanaConfig, core: KibanaCore); public ready(): Promise; public mixin(...fns: KbnMixinFunc[]): Promise; public listen(): Promise; public close(): Promise; - public afterPluginsInit(callback: () => void): void; public applyLoggingConfiguration(settings: any): void; public config: KibanaConfig; } diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index 107e5f6387833..e29563a7c6266 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -30,7 +30,6 @@ import { loggingMixin } from './logging'; import warningsMixin from './warnings'; import configCompleteMixin from './config/complete'; import { optimizeMixin } from '../../optimize'; -import * as Plugins from './plugins'; import { uiMixin } from '../ui'; import { i18nMixin } from './i18n'; @@ -47,9 +46,8 @@ export default class KbnServer { * @param {Record} settings * @param {KibanaConfig} config * @param {KibanaCore} core - * @param {LegacyPlugins} legacyPlugins */ - constructor(settings, config, core, legacyPlugins) { + constructor(settings, config, core) { this.name = pkg.name; this.version = pkg.version; this.build = pkg.build || false; @@ -74,14 +72,8 @@ export default class KbnServer { stop: null, }; - this.uiExports = legacyPlugins.uiExports; - this.pluginSpecs = legacyPlugins.pluginSpecs; - this.disabledPluginSpecs = legacyPlugins.disabledPluginSpecs; - this.ready = constant( this.mixin( - Plugins.waitForInitSetupMixin, - // Sets global HTTP behaviors httpMixin, @@ -93,22 +85,13 @@ export default class KbnServer { // scan translations dirs, register locale files and initialize i18n engine. i18nMixin, - // find plugins and set this.plugins and this.pluginSpecs - Plugins.scanMixin, - // tell the config we are done loading plugins configCompleteMixin, uiMixin, // setup routes that serve the @kbn/optimizer output - optimizeMixin, - - // initialize the plugins - Plugins.initializeMixin, - - // notify any deferred setup logic that plugins have initialized - Plugins.waitForInitResolveMixin + optimizeMixin ) ); diff --git a/src/legacy/server/plugins/index.js b/src/legacy/server/plugins/index.js deleted file mode 100644 index 1511b63b519ae..0000000000000 --- a/src/legacy/server/plugins/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { scanMixin } from './scan_mixin'; -export { initializeMixin } from './initialize_mixin'; -export { waitForInitSetupMixin, waitForInitResolveMixin } from './wait_for_plugins_init'; diff --git a/src/legacy/server/plugins/initialize_mixin.js b/src/legacy/server/plugins/initialize_mixin.js deleted file mode 100644 index ccf4cd1c1a404..0000000000000 --- a/src/legacy/server/plugins/initialize_mixin.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { callPluginHook } from './lib'; - -/** - * KbnServer mixin that initializes all plugins found in ./scan mixin - * @param {KbnServer} kbnServer - * @param {Hapi.Server} server - * @param {Config} config - * @return {Promise} - */ -export async function initializeMixin(kbnServer, server, config) { - if (!config.get('plugins.initialize')) { - server.log(['info'], 'Plugin initialization disabled.'); - return; - } - - async function callHookOnPlugins(hookName) { - const { plugins } = kbnServer; - const ids = plugins.map((p) => p.id); - - for (const id of ids) { - await callPluginHook(hookName, plugins, id, []); - } - } - - await callHookOnPlugins('preInit'); - await callHookOnPlugins('init'); - await callHookOnPlugins('postInit'); -} diff --git a/src/legacy/server/plugins/lib/call_plugin_hook.js b/src/legacy/server/plugins/lib/call_plugin_hook.js deleted file mode 100644 index b665869f5d25f..0000000000000 --- a/src/legacy/server/plugins/lib/call_plugin_hook.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { last } from 'lodash'; - -export async function callPluginHook(hookName, plugins, id, history) { - const plugin = plugins.find((plugin) => plugin.id === id); - - // make sure this is a valid plugin id - if (!plugin) { - if (history.length) { - throw new Error(`Unmet requirement "${id}" for plugin "${last(history)}"`); - } else { - throw new Error(`Unknown plugin "${id}"`); - } - } - - const circleStart = history.indexOf(id); - const path = [...history, id]; - - // make sure we are not trying to load a dependency within itself - if (circleStart > -1) { - const circle = path.slice(circleStart); - throw new Error(`circular dependency found: "${circle.join(' -> ')}"`); - } - - // call hook on all dependencies - for (const req of plugin.requiredIds) { - await callPluginHook(hookName, plugins, req, path); - } - - // call hook on this plugin - await plugin[hookName](); -} diff --git a/src/legacy/server/plugins/lib/call_plugin_hook.test.js b/src/legacy/server/plugins/lib/call_plugin_hook.test.js deleted file mode 100644 index 30dc2d91a9ab2..0000000000000 --- a/src/legacy/server/plugins/lib/call_plugin_hook.test.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import { callPluginHook } from './call_plugin_hook'; - -describe('server/plugins/callPluginHook', () => { - it('should call in correct order based on requirements', async () => { - const plugins = [ - { - id: 'foo', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['bar', 'baz'], - }, - { - id: 'bar', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: [], - }, - { - id: 'baz', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['bar'], - }, - ]; - - await callPluginHook('init', plugins, 'foo', []); - const [foo, bar, baz] = plugins; - sinon.assert.calledOnce(foo.init); - sinon.assert.calledTwice(bar.init); - sinon.assert.calledOnce(baz.init); - sinon.assert.callOrder(bar.init, baz.init, foo.init); - }); - - it('throws meaningful error when required plugin is missing', async () => { - const plugins = [ - { - id: 'foo', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['bar'], - }, - ]; - - try { - await callPluginHook('init', plugins, 'foo', []); - throw new Error('expected callPluginHook to throw'); - } catch (error) { - expect(error.message).toContain('"bar" for plugin "foo"'); - } - }); - - it('throws meaningful error when dependencies are circular', async () => { - const plugins = [ - { - id: 'foo', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['bar'], - }, - { - id: 'bar', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['baz'], - }, - { - id: 'baz', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['foo'], - }, - ]; - - try { - await callPluginHook('init', plugins, 'foo', []); - throw new Error('expected callPluginHook to throw'); - } catch (error) { - expect(error.message).toContain('foo -> bar -> baz -> foo'); - } - }); -}); diff --git a/src/legacy/server/plugins/lib/index.js b/src/legacy/server/plugins/lib/index.js deleted file mode 100644 index 2329d24498b6b..0000000000000 --- a/src/legacy/server/plugins/lib/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { callPluginHook } from './call_plugin_hook'; -export { Plugin } from './plugin'; diff --git a/src/legacy/server/plugins/lib/plugin.js b/src/legacy/server/plugins/lib/plugin.js deleted file mode 100644 index 48389061199ff..0000000000000 --- a/src/legacy/server/plugins/lib/plugin.js +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { once } from 'lodash'; - -/** - * The server plugin class, used to extend the server - * and add custom behavior. A "scoped" plugin class is - * created by the PluginApi class and provided to plugin - * providers that automatically binds all but the `opts` - * arguments. - * - * @class Plugin - * @param {KbnServer} kbnServer - the KbnServer this plugin - * belongs to. - * @param {PluginDefinition} def - * @param {PluginSpec} spec - */ -export class Plugin { - constructor(kbnServer, spec) { - this.kbnServer = kbnServer; - this.spec = spec; - this.pkg = spec.getPkg(); - this.path = spec.getPath(); - this.id = spec.getId(); - this.version = spec.getVersion(); - this.requiredIds = spec.getRequiredPluginIds() || []; - this.externalPreInit = spec.getPreInitHandler(); - this.externalInit = spec.getInitHandler(); - this.externalPostInit = spec.getPostInitHandler(); - this.enabled = spec.isEnabled(kbnServer.config); - this.configPrefix = spec.getConfigPrefix(); - this.publicDir = spec.getPublicDir(); - - this.preInit = once(this.preInit); - this.init = once(this.init); - this.postInit = once(this.postInit); - } - - async preInit() { - if (this.externalPreInit) { - return await this.externalPreInit(this.kbnServer.server); - } - } - - async init() { - const { id, version, kbnServer, configPrefix } = this; - const { config } = kbnServer; - - // setup the hapi register function and get on with it - const register = async (server, options) => { - this._server = server; - this._options = options; - - server.logWithMetadata(['plugins', 'debug'], `Initializing plugin ${this.toString()}`, { - plugin: this, - }); - - if (this.publicDir) { - server.newPlatform.__internals.http.registerStaticDir( - `/plugins/${id}/{path*}`, - this.publicDir - ); - } - - if (this.externalInit) { - await this.externalInit(server, options); - } - }; - - await kbnServer.server.register({ - plugin: { register, name: id, version }, - options: config.has(configPrefix) ? config.get(configPrefix) : null, - }); - } - - async postInit() { - if (this.externalPostInit) { - return await this.externalPostInit(this.kbnServer.server); - } - } - - getServer() { - return this._server; - } - - getOptions() { - return this._options; - } - - toJSON() { - return this.pkg; - } - - toString() { - return `${this.id}@${this.version}`; - } -} diff --git a/src/legacy/server/plugins/scan_mixin.js b/src/legacy/server/plugins/scan_mixin.js deleted file mode 100644 index 89ebaf920d9d1..0000000000000 --- a/src/legacy/server/plugins/scan_mixin.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { Plugin } from './lib'; - -export async function scanMixin(kbnServer) { - kbnServer.plugins = kbnServer.pluginSpecs.map((spec) => new Plugin(kbnServer, spec)); -} diff --git a/src/legacy/server/plugins/wait_for_plugins_init.js b/src/legacy/server/plugins/wait_for_plugins_init.js deleted file mode 100644 index 144eb5ef803cc..0000000000000 --- a/src/legacy/server/plugins/wait_for_plugins_init.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Tracks the individual queue for each kbnServer, rather than attaching - * it to the kbnServer object via a property or something - * @type {WeakMap} - */ -const queues = new WeakMap(); - -export function waitForInitSetupMixin(kbnServer) { - queues.set(kbnServer, []); - - kbnServer.afterPluginsInit = function (callback) { - const queue = queues.get(kbnServer); - - if (!queue) { - throw new Error( - 'Plugins have already initialized. Only use this method for setup logic that must wait for plugins to initialize.' - ); - } - - queue.push(callback); - }; -} - -export async function waitForInitResolveMixin(kbnServer, server, config) { - const queue = queues.get(kbnServer); - queues.set(kbnServer, null); - - // only actually call the callbacks if we are really initializing - if (config.get('plugins.initialize')) { - for (const cb of queue) { - await cb(); - } - } -} diff --git a/src/legacy/types.ts b/src/legacy/types.ts deleted file mode 100644 index 43c9ac79538b1..0000000000000 --- a/src/legacy/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './plugin_discovery/types'; diff --git a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js b/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js deleted file mode 100644 index afe618c6d3d9c..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Bluebird from 'bluebird'; - -export default (kibana) => - new kibana.Plugin({ - config(Joi) { - return Joi.object() - .keys({ - enabled: Joi.boolean().default(true), - delay: Joi.number().required(), - shared: Joi.string(), - }) - .default(); - }, - - uiExports: { - async injectDefaultVars(server, options) { - await Bluebird.delay(options.delay); - return { shared: options.shared }; - }, - }, - }); diff --git a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/package.json b/src/legacy/ui/__tests__/fixtures/plugin_async_foo/package.json deleted file mode 100644 index fc1c8d8088f1b..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "plugin_async_foo", - "version": "kibana" -} diff --git a/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js b/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js deleted file mode 100644 index 975a1dc7c92e7..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default (kibana) => - new kibana.Plugin({ - config(Joi) { - return Joi.object() - .keys({ - enabled: Joi.boolean().default(true), - shared: Joi.string(), - }) - .default(); - }, - - uiExports: { - injectDefaultVars(server, options) { - return { shared: options.shared }; - }, - }, - }); diff --git a/src/legacy/ui/__tests__/fixtures/plugin_bar/package.json b/src/legacy/ui/__tests__/fixtures/plugin_bar/package.json deleted file mode 100644 index f79b807990dca..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_bar/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "plugin_bar", - "version": "kibana" -} diff --git a/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js b/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js deleted file mode 100644 index 975a1dc7c92e7..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default (kibana) => - new kibana.Plugin({ - config(Joi) { - return Joi.object() - .keys({ - enabled: Joi.boolean().default(true), - shared: Joi.string(), - }) - .default(); - }, - - uiExports: { - injectDefaultVars(server, options) { - return { shared: options.shared }; - }, - }, - }); diff --git a/src/legacy/ui/__tests__/fixtures/plugin_foo/package.json b/src/legacy/ui/__tests__/fixtures/plugin_foo/package.json deleted file mode 100644 index c1b7ddd35c9a2..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "plugin_foo", - "version": "kibana" -} diff --git a/src/legacy/ui/__tests__/fixtures/test_app/index.js b/src/legacy/ui/__tests__/fixtures/test_app/index.js deleted file mode 100644 index 3eddefd618ce0..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/test_app/index.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default (kibana) => - new kibana.Plugin({ - uiExports: { - app: { - name: 'test_app', - main: 'plugins/test_app/index.js', - }, - - injectDefaultVars() { - return { - from_defaults: true, - }; - }, - }, - init(server) { - server.injectUiAppVars('test_app', () => ({ - from_test_app: true, - })); - }, - }); diff --git a/src/legacy/ui/__tests__/fixtures/test_app/package.json b/src/legacy/ui/__tests__/fixtures/test_app/package.json deleted file mode 100644 index 3aeb029e4f4cc..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/test_app/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "test_app", - "version": "kibana" -} diff --git a/src/legacy/ui/index.js b/src/legacy/ui/index.js index 05373fa5d1964..5c06cb4677347 100644 --- a/src/legacy/ui/index.js +++ b/src/legacy/ui/index.js @@ -18,4 +18,3 @@ */ export { uiMixin } from './ui_mixin'; -export { collectUiExports } from './ui_exports'; diff --git a/src/legacy/ui/ui_exports/README.md b/src/legacy/ui/ui_exports/README.md deleted file mode 100644 index 7fb117b1c25b9..0000000000000 --- a/src/legacy/ui/ui_exports/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# UI Exports - -When defining a Plugin, the `uiExports` key can be used to define a map of export types to values that will be used to configure the UI system. A common use for `uiExports` is `uiExports.app`, which defines the configuration of a [`UiApp`][UiApp] and teaches the UI System how to render, bundle and tell the user about an application. - - -## `collectUiExports(pluginSpecs): { [type: string]: any }` - -This function produces the object commonly found at `kbnServer.uiExports`. This object is created by calling `collectPluginExports()` with a standard set of export type reducers and defaults for the UI System. - -### export type reducers - -The [`ui_export_types` module][UiExportTypes] defines the reducer used for each uiExports key (or `type`). The name of every export in [./ui_export_types/index.js][UiExportTypes] is a key that plugins can define in their `uiExports` specification and the value of those exports are reducers that `collectPluginExports()` will call to produce the merged result of all export specs. - -### example - UiApps - -Plugin authors can define a new UiApp in their plugin specification like so: - -```js -// a single app export -export default function (kibana) { - return new kibana.Plugin({ - //... - uiExports: { - app: { - // uiApp spec options go here - } - } - }) -} - -// apps can also export multiple apps -export default function (kibana) { - return new kibana.Plugin({ - //... - uiExports: { - apps: [ - { /* uiApp spec options */ }, - { /* second uiApp spec options */ }, - ] - } - }) -} -``` - -To handle this export type, the [ui_export_types][UiExportTypes] module exports two reducers, one named `app` and the other `apps`. - -```js -export const app = ... -export const apps = ... -``` - -These reducers are defined in [`ui_export_types/ui_apps`][UiAppExportType] and have the exact same definition: - -```js -// `wrap()` produces a reducer by wrapping a base reducer with modifiers. -// All but the last argument are modifiers that take a reducer and return -// an alternate reducer to use in it's place. -// -// Most wrappers call their target reducer with slightly different -// arguments. This allows composing standard reducer modifications for -// reuse, consistency, and easy reference (once you get the hang of it). -wrap( - // calls the next reducer with the `type` set to `uiAppSpecs`, ignoring - // the key the plugin author used to define this spec ("app" or "apps" - // in this example) - alias('uiAppSpecs'), - - // calls the next reducer with the `spec` set to the result of calling - // `applySpecDefaults(spec, type, pluginSpec)` which merges some defaults - // from the `PluginSpec` because we want uiAppSpecs to be useful individually - mapSpec(applySpecDefaults), - - // writes this spec to `acc[type]` (`acc.uiAppSpecs` in this example since - // the type was set to `uiAppSpecs` by `alias()`). It does this by concatenating - // the current value and the spec into an array. If either item is already - // an array its items are added to the result individually. If either item - // is undefined it is ignored. - // - // NOTE: since flatConcatAtType is last it isn't a wrapper, it's - // just a normal reducer - flatConcatAtType -) -``` - -This reducer format was chosen so that it will be easier to look back at these reducers and see that `app` and `apps` export specs are written to `kbnServer.uiExports.uiAppSpecs`, with defaults applied, in an array. - -### defaults - -The [`ui_exports/ui_export_defaults`][UiExportDefaults] module defines the default shape of the uiExports object produced by `collectUiExports()`. The defaults generally describe the `uiExports` from the UI System itself, like default visTypes and such. - -[UiExportDefaults]: ./ui_export_defaults.js "uiExport defaults definition" -[UiExportTypes]: ./ui_export_types/index.js "Index of default ui_export_types module" -[UiAppExportType]: ./ui_export_types/ui_apps.js "UiApp extension type definition" -[PluginSpec]: ../../plugin_discovery/plugin_spec/plugin_spec.js "PluginSpec class definition" -[PluginDiscovery]: '../../plugin_discovery' "plugin_discovery module" \ No newline at end of file diff --git a/src/legacy/ui/ui_exports/collect_ui_exports.ts b/src/legacy/ui/ui_exports/collect_ui_exports.ts deleted file mode 100644 index edb2a11dc0527..0000000000000 --- a/src/legacy/ui/ui_exports/collect_ui_exports.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { LegacyUiExports } from '../../../core/server'; - -// @ts-ignore -import { UI_EXPORT_DEFAULTS } from './ui_export_defaults'; -// @ts-ignore -import * as uiExportTypeReducers from './ui_export_types'; -// @ts-ignore -import { reduceExportSpecs } from '../../plugin_discovery'; - -export function collectUiExports(pluginSpecs: unknown[]): LegacyUiExports { - return reduceExportSpecs(pluginSpecs, uiExportTypeReducers, UI_EXPORT_DEFAULTS); -} diff --git a/src/legacy/ui/ui_exports/index.js b/src/legacy/ui/ui_exports/index.js deleted file mode 100644 index 56db698dc7b03..0000000000000 --- a/src/legacy/ui/ui_exports/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { collectUiExports } from './collect_ui_exports'; diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js deleted file mode 100644 index 227954155ce88..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export const UI_EXPORT_DEFAULTS = {}; diff --git a/src/legacy/ui/ui_exports/ui_export_types/index.js b/src/legacy/ui/ui_exports/ui_export_types/index.js deleted file mode 100644 index 9ff6a53f4afb9..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/index.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { injectDefaultVars, replaceInjectedVars } from './modify_injected_vars'; - -export { - mappings, - migrations, - savedObjectSchemas, - savedObjectsManagement, - validations, -} from './saved_object'; - -export { taskDefinitions } from './task_definitions'; - -export { link, links } from './ui_nav_links'; - -export { uiSettingDefaults } from './ui_settings'; - -export { unknown } from './unknown'; diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_injected_vars.js b/src/legacy/ui/ui_exports/ui_export_types/modify_injected_vars.js deleted file mode 100644 index 4bb9f350bd959..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_injected_vars.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { flatConcatAtType } from './reduce'; -import { wrap, alias, mapSpec } from './modify_reduce'; - -export const replaceInjectedVars = wrap(alias('injectedVarsReplacers'), flatConcatAtType); - -export const injectDefaultVars = wrap( - alias('defaultInjectedVarProviders'), - mapSpec((spec, type, pluginSpec) => ({ - pluginSpec, - fn: spec, - })), - flatConcatAtType -); diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js deleted file mode 100644 index a894e59a03c81..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Creates a reducer wrapper which, when called with a reducer, creates a new - * reducer that replaces the `type` value with `newType` before delegating to - * the wrapped reducer - * @param {String} newType - * @return {Function} - */ -export const alias = (newType) => (next) => (acc, spec, type, pluginSpec) => - next(acc, spec, newType, pluginSpec); diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/debug.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/debug.js deleted file mode 100644 index c40bca59fe14c..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/debug.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { mapSpec } from './map_spec'; - -/** - * Reducer wrapper which, replaces the `spec` with the details about the definition - * of that spec - * @type {Function} - */ -export const debug = mapSpec((spec, type, pluginSpec) => ({ - spec, - type, - pluginSpec, -})); diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/index.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/index.js deleted file mode 100644 index 54c81fefdd08a..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/index.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { alias } from './alias'; -export { debug } from './debug'; -export { mapSpec } from './map_spec'; -export { wrap } from './wrap'; -export { uniqueKeys } from './unique_keys'; diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js deleted file mode 100644 index 5970c45e7445e..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Creates a reducer wrapper which, when called with a reducer, creates a new - * reducer that replaces the `specs` value with the result of calling - * `mapFn(spec, type, pluginSpec)` before delegating to the wrapped - * reducer - * @param {Function} mapFn receives `(specs, type, pluginSpec)` - * @return {Function} - */ -export const mapSpec = (mapFn) => (next) => (acc, spec, type, pluginSpec) => - next(acc, mapFn(spec, type, pluginSpec), type, pluginSpec); diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js deleted file mode 100644 index dedcd057b09e3..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const pluginId = (pluginSpec) => (pluginSpec.id ? pluginSpec.id() : pluginSpec.getId()); - -export const uniqueKeys = (sourceType) => (next) => (acc, spec, type, pluginSpec) => { - const duplicates = Object.keys(spec).filter((key) => acc[type] && acc[type].hasOwnProperty(key)); - - if (duplicates.length) { - throw new Error( - `${pluginId(pluginSpec)} defined duplicate ${sourceType || type} values: ${duplicates}` - ); - } - - return next(acc, spec, type, pluginSpec); -}; diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/wrap.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/wrap.js deleted file mode 100644 index f84d83ed7c845..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/wrap.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Wrap a function with any number of wrappers. Wrappers - * are functions that take a reducer and return a reducer - * that should be called in its place. The wrappers will - * be called in reverse order for setup and then in the - * order they are defined when the resulting reducer is - * executed. - * - * const reducer = wrap( - * next => (acc) => acc[1] = 'a', - * next => (acc) => acc[1] = 'b', - * next => (acc) => acc[1] = 'c' - * ) - * - * reducer('foo') //=> 'fco' - * - * @param {Function} ...wrappers - * @param {Function} reducer - * @return {Function} - */ -export function wrap(...args) { - const reducer = args[args.length - 1]; - const wrappers = args.slice(0, -1); - - return wrappers.reverse().reduce((acc, wrapper) => wrapper(acc), reducer); -} diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_at_type.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_at_type.js deleted file mode 100644 index 5fcbcac463392..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_at_type.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { createTypeReducer, flatConcat } from './lib'; - -/** - * Reducer that merges two values concatenating all values - * into a flattened array - * @param {Any} [initial] - * @return {Function} - */ -export const flatConcatAtType = createTypeReducer(flatConcat); diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/index.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/index.js deleted file mode 100644 index 7dc1ba60fb3cb..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { mergeAtType } from './merge_at_type'; -export { flatConcatValuesAtType } from './flat_concat_values_at_type'; -export { flatConcatAtType } from './flat_concat_at_type'; diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js deleted file mode 100644 index bf4793c208308..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Creates a reducer that reduces the values within `acc[type]` by calling - * reducer with signature: - * - * reducer(acc[type], spec, type, pluginSpec) - * - * @param {Function} reducer - * @return {Function} - */ -export const createTypeReducer = (reducer) => (acc, spec, type, pluginSpec) => ({ - ...acc, - [type]: reducer(acc[type], spec, type, pluginSpec), -}); diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/flat_concat.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/flat_concat.js deleted file mode 100644 index 1337c8a85d5b4..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/flat_concat.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Concatenate two values into a single array, ignoring either - * value if it is undefined and flattening the value if it is an array - * @param {Array|T} a - * @param {Array} b - * @return {Array} - */ -export const flatConcat = (a, b) => [].concat(a === undefined ? [] : a, b === undefined ? [] : b); diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/index.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/index.js deleted file mode 100644 index e4281caebe245..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { flatConcat } from './flat_concat'; -export { mergeWith } from './merge_with'; -export { createTypeReducer } from './create_type_reducer'; diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/merge_with.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/merge_with.js deleted file mode 100644 index 6c7d31e6fd74d..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/merge_with.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const uniqueConcat = (arrayA, arrayB) => - arrayB.reduce((acc, key) => (acc.includes(key) ? acc : acc.concat(key)), arrayA); - -/** - * Assign the keys from both objA and objB to target after passing the - * current and new value through merge as `(target[key], source[key])` - * @param {Object} objA - * @param {Object} objB - * @param {Function} merge - * @return {Object} target - */ -export function mergeWith(objA, objB, merge) { - const target = {}; - const keys = uniqueConcat(Object.keys(objA), Object.keys(objB)); - for (const key of keys) { - target[key] = merge(objA[key], objB[key]); - } - return target; -} diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/merge_at_type.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/merge_at_type.js deleted file mode 100644 index 4f5a501253851..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/merge_at_type.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { createTypeReducer } from './lib'; - -export const mergeAtType = createTypeReducer((a, b) => ({ - ...a, - ...b, -})); diff --git a/src/legacy/ui/ui_exports/ui_export_types/saved_object.js b/src/legacy/ui/ui_exports/ui_export_types/saved_object.js deleted file mode 100644 index be6898d3e642c..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/saved_object.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { flatConcatAtType, mergeAtType } from './reduce'; -import { alias, mapSpec, uniqueKeys, wrap } from './modify_reduce'; - -// mapping types -export const mappings = wrap( - alias('savedObjectMappings'), - mapSpec((spec, type, pluginSpec) => ({ - pluginId: pluginSpec.getId(), - properties: spec, - })), - flatConcatAtType -); - -const pluginId = (pluginSpec) => (pluginSpec.id ? pluginSpec.id() : pluginSpec.getId()); - -// Combines the `migrations` property of each plugin, -// ensuring that properties are unique across plugins -// and has migrations defined where the mappings are defined. -// See saved_objects/migrations for more details. -export const migrations = wrap( - alias('savedObjectMigrations'), - (next) => (acc, spec, type, pluginSpec) => { - const mappings = pluginSpec.getExportSpecs().mappings || {}; - const invalidMigrationTypes = Object.keys(spec).filter((type) => !mappings[type]); - if (invalidMigrationTypes.length) { - throw new Error( - 'Migrations and mappings must be defined together in the uiExports of a single plugin. ' + - `${pluginId(pluginSpec)} defines migrations for types ${invalidMigrationTypes.join( - ', ' - )} but does not define their mappings.` - ); - } - return next(acc, spec, type, pluginSpec); - }, - uniqueKeys(), - mergeAtType -); - -export const savedObjectSchemas = wrap(uniqueKeys(), mergeAtType); - -export const savedObjectsManagement = wrap(uniqueKeys(), mergeAtType); - -// Combines the `validations` property of each plugin, -// ensuring that properties are unique across plugins. -// See saved_objects/validation for more details. -export const validations = wrap(alias('savedObjectValidations'), uniqueKeys(), mergeAtType); diff --git a/src/legacy/ui/ui_exports/ui_export_types/task_definitions.js b/src/legacy/ui/ui_exports/ui_export_types/task_definitions.js deleted file mode 100644 index 8a0ed85d86f3e..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/task_definitions.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { mergeAtType } from './reduce'; -import { alias, wrap, uniqueKeys } from './modify_reduce'; - -// How plugins define tasks that the task manager can run. -export const taskDefinitions = wrap(alias('taskDefinitions'), uniqueKeys(), mergeAtType); diff --git a/src/legacy/ui/ui_exports/ui_export_types/ui_nav_links.js b/src/legacy/ui/ui_exports/ui_export_types/ui_nav_links.js deleted file mode 100644 index 34aff7463a249..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/ui_nav_links.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { flatConcatAtType } from './reduce'; -import { wrap, alias } from './modify_reduce'; - -export const links = wrap(alias('navLinkSpecs'), flatConcatAtType); -export const link = wrap(alias('navLinkSpecs'), flatConcatAtType); diff --git a/src/legacy/ui/ui_exports/ui_export_types/ui_settings.js b/src/legacy/ui/ui_exports/ui_export_types/ui_settings.js deleted file mode 100644 index 8d88490579c21..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/ui_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { mergeAtType } from './reduce'; -import { wrap, uniqueKeys } from './modify_reduce'; - -export const uiSettingDefaults = wrap(uniqueKeys(), mergeAtType); diff --git a/src/legacy/ui/ui_exports/ui_export_types/unknown.js b/src/legacy/ui/ui_exports/ui_export_types/unknown.js deleted file mode 100644 index a12a514d2e6bf..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/unknown.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { flatConcatAtType } from './reduce'; -import { wrap, alias, debug } from './modify_reduce'; - -export const unknown = wrap(debug, alias('unknown'), flatConcatAtType); diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index e3b7c1e0c3ff9..2983dbbc28667 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -67,115 +67,108 @@ export function uiRenderMixin(kbnServer, server, config) { }, }); - // register the bootstrap.js route after plugins are initialized so that we can - // detect if any default auth strategies were registered - kbnServer.afterPluginsInit(() => { - const authEnabled = !!server.auth.settings.default; - - server.route({ - path: '/bootstrap.js', - method: 'GET', - config: { - tags: ['api'], - auth: authEnabled ? { mode: 'try' } : false, - }, - async handler(request, h) { - const soClient = kbnServer.newPlatform.start.core.savedObjects.getScopedClient( - KibanaRequest.from(request) - ); - const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient(soClient); - - const darkMode = - !authEnabled || request.auth.isAuthenticated - ? await uiSettings.get('theme:darkMode') - : false; - - const themeVersion = - !authEnabled || request.auth.isAuthenticated - ? await uiSettings.get('theme:version') - : 'v7'; - - const themeTag = `${themeVersion === 'v7' ? 'v7' : 'v8'}${darkMode ? 'dark' : 'light'}`; - - const buildHash = server.newPlatform.env.packageInfo.buildNum; - const basePath = config.get('server.basePath'); - - const regularBundlePath = `${basePath}/${buildHash}/bundles`; - - const styleSheetPaths = [ - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, - ...(darkMode - ? [ - themeVersion === 'v7' - ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}` - : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkV8CssDistFilename}`, - `${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`, - `${basePath}/ui/legacy_dark_theme.css`, - ] - : [ - themeVersion === 'v7' - ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}` - : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightV8CssDistFilename}`, - `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, - `${basePath}/ui/legacy_light_theme.css`, - ]), - ]; - - const kpUiPlugins = kbnServer.newPlatform.__internals.uiPlugins; - const kpPluginPublicPaths = new Map(); - const kpPluginBundlePaths = new Set(); - - // recursively iterate over the kpUiPlugin ids and their required bundles - // to populate kpPluginPublicPaths and kpPluginBundlePaths - (function readKpPlugins(ids) { - for (const id of ids) { - if (kpPluginPublicPaths.has(id)) { - continue; - } - - kpPluginPublicPaths.set(id, `${regularBundlePath}/plugin/${id}/`); - kpPluginBundlePaths.add(`${regularBundlePath}/plugin/${id}/${id}.plugin.js`); - readKpPlugins(kpUiPlugins.internal.get(id).requiredBundles); + const authEnabled = !!server.auth.settings.default; + server.route({ + path: '/bootstrap.js', + method: 'GET', + config: { + tags: ['api'], + auth: authEnabled ? { mode: 'try' } : false, + }, + async handler(request, h) { + const soClient = kbnServer.newPlatform.start.core.savedObjects.getScopedClient( + KibanaRequest.from(request) + ); + const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient(soClient); + + const darkMode = + !authEnabled || request.auth.isAuthenticated + ? await uiSettings.get('theme:darkMode') + : false; + + const themeVersion = + !authEnabled || request.auth.isAuthenticated ? await uiSettings.get('theme:version') : 'v7'; + + const themeTag = `${themeVersion === 'v7' ? 'v7' : 'v8'}${darkMode ? 'dark' : 'light'}`; + + const buildHash = server.newPlatform.env.packageInfo.buildNum; + const basePath = config.get('server.basePath'); + + const regularBundlePath = `${basePath}/${buildHash}/bundles`; + + const styleSheetPaths = [ + `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, + ...(darkMode + ? [ + themeVersion === 'v7' + ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}` + : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkV8CssDistFilename}`, + `${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`, + `${basePath}/ui/legacy_dark_theme.css`, + ] + : [ + themeVersion === 'v7' + ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}` + : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightV8CssDistFilename}`, + `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, + `${basePath}/ui/legacy_light_theme.css`, + ]), + ]; + + const kpUiPlugins = kbnServer.newPlatform.__internals.uiPlugins; + const kpPluginPublicPaths = new Map(); + const kpPluginBundlePaths = new Set(); + + // recursively iterate over the kpUiPlugin ids and their required bundles + // to populate kpPluginPublicPaths and kpPluginBundlePaths + (function readKpPlugins(ids) { + for (const id of ids) { + if (kpPluginPublicPaths.has(id)) { + continue; } - })(kpUiPlugins.public.keys()); - - const jsDependencyPaths = [ - ...UiSharedDeps.jsDepFilenames.map( - (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` - ), - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, - - `${regularBundlePath}/core/core.entry.js`, - ...kpPluginBundlePaths, - ]; - - // These paths should align with the bundle routes configured in - // src/optimize/bundles_route/bundles_route.ts - const publicPathMap = JSON.stringify({ - core: `${regularBundlePath}/core/`, - 'kbn-ui-shared-deps': `${regularBundlePath}/kbn-ui-shared-deps/`, - ...Object.fromEntries(kpPluginPublicPaths), - }); - - const bootstrap = new AppBootstrap({ - templateData: { - themeTag, - jsDependencyPaths, - styleSheetPaths, - publicPathMap, - }, - }); - - const body = await bootstrap.getJsFile(); - const etag = await bootstrap.getJsFileHash(); - - return h - .response(body) - .header('cache-control', 'must-revalidate') - .header('content-type', 'application/javascript') - .etag(etag); - }, - }); + + kpPluginPublicPaths.set(id, `${regularBundlePath}/plugin/${id}/`); + kpPluginBundlePaths.add(`${regularBundlePath}/plugin/${id}/${id}.plugin.js`); + readKpPlugins(kpUiPlugins.internal.get(id).requiredBundles); + } + })(kpUiPlugins.public.keys()); + + const jsDependencyPaths = [ + ...UiSharedDeps.jsDepFilenames.map( + (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` + ), + `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, + + `${regularBundlePath}/core/core.entry.js`, + ...kpPluginBundlePaths, + ]; + + // These paths should align with the bundle routes configured in + // src/optimize/bundles_route/bundles_route.ts + const publicPathMap = JSON.stringify({ + core: `${regularBundlePath}/core/`, + 'kbn-ui-shared-deps': `${regularBundlePath}/kbn-ui-shared-deps/`, + ...Object.fromEntries(kpPluginPublicPaths), + }); + + const bootstrap = new AppBootstrap({ + templateData: { + themeTag, + jsDependencyPaths, + styleSheetPaths, + publicPathMap, + }, + }); + + const body = await bootstrap.getJsFile(); + const etag = await bootstrap.getJsFileHash(); + + return h + .response(body) + .header('cache-control', 'must-revalidate') + .header('content-type', 'application/javascript') + .etag(etag); + }, }); server.route({ @@ -191,19 +184,17 @@ export function uiRenderMixin(kbnServer, server, config) { }); async function renderApp(h) { - const app = { getId: () => 'core' }; const { http } = kbnServer.newPlatform.setup.core; const { savedObjects } = kbnServer.newPlatform.start.core; - const { rendering, legacy } = kbnServer.newPlatform.__internals; + const { rendering } = kbnServer.newPlatform.__internals; const req = KibanaRequest.from(h.request); const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient( savedObjects.getScopedClient(req) ); - const vars = await legacy.getVars(app.getId(), h.request, { + const vars = { apmConfig: getApmConfig(h.request.path), - }); + }; const content = await rendering.render(h.request, uiSettings, { - app, includeUserSettings: true, vars, }); diff --git a/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts b/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts index d2a4ee8297a11..a74223f28dd03 100644 --- a/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts +++ b/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts @@ -37,7 +37,7 @@ export const createNodeAgentInstructions = (apmServerUrl = '', secretToken = '') defaultMessage: 'Agents are libraries that run inside of your application process. \ APM services are created programmatically based on the `serviceName`. \ -This agent supports a vararity of frameworks but can also be used with your custom stack.', +This agent supports a variety of frameworks but can also be used with your custom stack.', }), commands: `// ${i18n.translate( 'apmOss.tutorial.nodeClient.configure.commands.addThisToTheFileTopComment', diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx index dd5ef5209a244..44ed5f4b8051e 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx @@ -20,7 +20,7 @@ import { EuiScreenReaderOnly } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useRef } from 'react'; -import { expandLiteralStrings } from '../../../../../../../es_ui_shared/public'; +import { expandLiteralStrings } from '../../../../../shared_imports'; import { useEditorReadContext, useRequestReadContext, diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts b/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts index cfbd5691bc22b..d01adf332e24a 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts @@ -18,7 +18,8 @@ */ import { extractDeprecationMessages } from '../../../lib/utils'; -import { collapseLiteralStrings } from '../../../../../es_ui_shared/public'; +import { XJson } from '../../../../../es_ui_shared/public'; +const { collapseLiteralStrings } = XJson; // @ts-ignore import * as es from '../../../lib/es/es'; import { BaseResponseType } from '../../../types'; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/input_highlight_rules.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/input_highlight_rules.js index 1558cf0cb5554..bc0129850f299 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/input_highlight_rules.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/input_highlight_rules.js @@ -18,7 +18,7 @@ */ import ace from 'brace'; -import { addXJsonToRules } from '../../../../../../es_ui_shared/public'; +import { addXJsonToRules } from '@kbn/ace'; export function addEOL(tokens, reg, nextIfEOL, normalNext) { if (typeof reg === 'object') { diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.js index 448fd847aeacd..2f39689319389 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.js @@ -19,7 +19,7 @@ import ace from 'brace'; import 'brace/mode/json'; -import { addXJsonToRules } from '../../../../../../es_ui_shared/public'; +import { addXJsonToRules } from '@kbn/ace'; const oop = ace.acequire('ace/lib/oop'); const JsonHighlightRules = ace.acequire('ace/mode/json_highlight_rules').JsonHighlightRules; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js index 6079c9db40eef..03d5b10f82d01 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js @@ -18,7 +18,7 @@ */ import ace from 'brace'; -import { ScriptHighlightRules } from '../../../../../../es_ui_shared/public'; +import { ScriptHighlightRules } from '@kbn/ace'; const oop = ace.acequire('ace/lib/oop'); const TextMode = ace.acequire('ace/mode/text').Mode; diff --git a/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js b/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js index c3fb879f2eeeb..04d3cd1a724e1 100644 --- a/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js +++ b/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js @@ -22,9 +22,11 @@ import $ from 'jquery'; import _ from 'lodash'; import { create } from '../create'; -import { collapseLiteralStrings } from '../../../../../../es_ui_shared/public'; +import { XJson } from '../../../../../../es_ui_shared/public'; import editorInput1 from './editor_input1.txt'; +const { collapseLiteralStrings } = XJson; + describe('Editor', () => { let input; diff --git a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts index dbf4f1adcba0a..66324050bc2fa 100644 --- a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts +++ b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; import RowParser from '../../../lib/row_parser'; -import { collapseLiteralStrings } from '../../../../../es_ui_shared/public'; +import { XJson } from '../../../../../es_ui_shared/public'; import * as utils from '../../../lib/utils'; // @ts-ignore @@ -30,6 +30,8 @@ import { createTokenIterator } from '../../factories'; import Autocomplete from '../../../lib/autocomplete/autocomplete'; +const { collapseLiteralStrings } = XJson; + export class SenseEditor { currentReqRange: (Range & { markerRef: any }) | null; parser: any; diff --git a/src/plugins/console/public/lib/utils/index.ts b/src/plugins/console/public/lib/utils/index.ts index 917988e0e811b..b95680e5df47e 100644 --- a/src/plugins/console/public/lib/utils/index.ts +++ b/src/plugins/console/public/lib/utils/index.ts @@ -18,7 +18,9 @@ */ import _ from 'lodash'; -import { expandLiteralStrings, collapseLiteralStrings } from '../../../../es_ui_shared/public'; +import { XJson } from '../../../../es_ui_shared/public'; + +const { collapseLiteralStrings, expandLiteralStrings } = XJson; export function textFromRequest(request: any) { let data = request.data; diff --git a/src/plugins/console/public/shared_imports.ts b/src/plugins/console/public/shared_imports.ts index aa64091903fb7..36c50f9c51e0d 100644 --- a/src/plugins/console/public/shared_imports.ts +++ b/src/plugins/console/public/shared_imports.ts @@ -17,6 +17,8 @@ * under the License. */ -import { sendRequest } from '../../es_ui_shared/public'; +import { sendRequest, XJson } from '../../es_ui_shared/public'; -export { sendRequest }; +const { collapseLiteralStrings, expandLiteralStrings } = XJson; + +export { sendRequest, collapseLiteralStrings, expandLiteralStrings }; diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx index 974b55275ccc1..bff0236c802f1 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx @@ -79,6 +79,7 @@ export class LibraryNotificationAction implements ActionByType { return ( + embeddable.getRoot().isContainer && embeddable.getInput()?.viewMode !== ViewMode.VIEW && isReferenceOrValueEmbeddable(embeddable) && embeddable.inputIsRefType(embeddable.getInput()) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index dd5eb1ee5ccaa..e5b467a418177 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -66,7 +66,6 @@ import { ViewMode, ContainerOutput, EmbeddableInput, - SavedObjectEmbeddableInput, } from '../../../embeddable/public'; import { NavAction, SavedDashboardPanel } from '../types'; @@ -178,7 +177,7 @@ export class DashboardAppController { chrome.docTitle.change(dash.title); } - const incomingEmbeddable = embeddable + let incomingEmbeddable = embeddable .getStateTransfer(scopedHistory()) .getIncomingEmbeddablePackage(); @@ -344,6 +343,22 @@ export class DashboardAppController { dashboardStateManager.getPanels().forEach((panel: SavedDashboardPanel) => { embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel); }); + + // If the incoming embeddable state's id already exists in the embeddables map, replace the input, retaining the existing gridData for that panel. + if (incomingEmbeddable?.embeddableId && embeddablesMap[incomingEmbeddable.embeddableId]) { + const originalPanelState = embeddablesMap[incomingEmbeddable.embeddableId]; + embeddablesMap[incomingEmbeddable.embeddableId] = { + gridData: originalPanelState.gridData, + type: incomingEmbeddable.type, + explicitInput: { + ...originalPanelState.explicitInput, + ...incomingEmbeddable.input, + id: incomingEmbeddable.embeddableId, + }, + }; + incomingEmbeddable = undefined; + } + let expandedPanelId; if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { expandedPanelId = dashboardContainer.getInput().expandedPanelId; @@ -482,32 +497,16 @@ export class DashboardAppController { refreshDashboardContainer(); }); - if (incomingEmbeddable) { - if ('id' in incomingEmbeddable) { - container.addOrUpdateEmbeddable( - incomingEmbeddable.type, - { - savedObjectId: incomingEmbeddable.id, - } - ); - } else if ('input' in incomingEmbeddable) { - const input = incomingEmbeddable.input; - // @ts-expect-error - delete input.id; - const explicitInput = { - savedVis: input, - }; - const embeddableId = - 'embeddableId' in incomingEmbeddable - ? incomingEmbeddable.embeddableId - : undefined; - container.addOrUpdateEmbeddable( - incomingEmbeddable.type, - // This ugly solution is temporary - https://github.com/elastic/kibana/pull/70272 fixes this whole section - (explicitInput as unknown) as EmbeddableInput, - embeddableId - ); - } + // If the incomingEmbeddable does not yet exist in the panels listing, create a new panel using the container's addEmbeddable method. + if ( + incomingEmbeddable && + (!incomingEmbeddable.embeddableId || + !container.getInput().panels[incomingEmbeddable.embeddableId]) + ) { + container.addNewEmbeddable( + incomingEmbeddable.type, + incomingEmbeddable.input + ); } } diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service_mock.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx similarity index 100% rename from src/plugins/dashboard/public/attribute_service/attribute_service_mock.tsx rename to src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts b/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts index 06f380ca3862b..ae8f034aec687 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts +++ b/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts @@ -18,7 +18,7 @@ */ import { ATTRIBUTE_SERVICE_KEY } from './attribute_service'; -import { mockAttributeService } from './attribute_service_mock'; +import { mockAttributeService } from './attribute_service.mock'; import { coreMock } from '../../../../core/public/mocks'; interface TestAttributes { diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx index 84df05154fb63..7499a6fced72a 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx +++ b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx @@ -156,12 +156,8 @@ export class AttributeService< }; public getExplicitInputFromEmbeddable(embeddable: IEmbeddable): ValType | RefType { - return embeddable.getRoot() && - (embeddable.getRoot() as Container).getInput().panels[embeddable.id].explicitInput - ? ((embeddable.getRoot() as Container).getInput().panels[embeddable.id].explicitInput as - | ValType - | RefType) - : (embeddable.getInput() as ValType | RefType); + return ((embeddable.getRoot() as Container).getInput()?.panels?.[embeddable.id] + ?.explicitInput ?? embeddable.getInput()) as ValType | RefType; } getInputAsValueType = async (input: ValType | RefType): Promise => { @@ -204,7 +200,14 @@ export class AttributeService< const newAttributes = { ...input[ATTRIBUTE_SERVICE_KEY] }; newAttributes.title = props.newTitle; const wrappedInput = (await this.wrapAttributes(newAttributes, true)) as RefType; - resolve(wrappedInput); + + // Remove unneeded attributes from the original input. + delete (input as { [ATTRIBUTE_SERVICE_KEY]?: SavedObjectAttributes })[ + ATTRIBUTE_SERVICE_KEY + ]; + + // Combine input and wrapped input to preserve any passed in explicit Input. + resolve({ ...input, ...wrappedInput }); return { id: wrappedInput.savedObjectId }; } catch (error) { reject(error); diff --git a/src/plugins/es_ui_shared/public/monaco/index.ts b/src/plugins/dashboard/public/attribute_service/index.ts similarity index 91% rename from src/plugins/es_ui_shared/public/monaco/index.ts rename to src/plugins/dashboard/public/attribute_service/index.ts index 23ba93e913234..84d4c8a13c31e 100644 --- a/src/plugins/es_ui_shared/public/monaco/index.ts +++ b/src/plugins/dashboard/public/attribute_service/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { useXJsonMode } from '../../__packages_do_not_import__/monaco'; +export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service'; diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index e22d1f038a456..315afd61c7c44 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -31,7 +31,7 @@ export { } from './application'; export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; -export { DashboardStart, DashboardUrlGenerator } from './plugin'; +export { DashboardStart, DashboardUrlGenerator, DashboardFeatureFlagConfig } from './plugin'; export { DASHBOARD_APP_URL_GENERATOR, createDashboardUrlGenerator, @@ -40,7 +40,7 @@ export { export { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; export { SavedObjectDashboard } from './saved_dashboards'; export { SavedDashboardPanel } from './types'; -export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service/attribute_service'; +export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service'; export function plugin(initializerContext: PluginInitializerContext) { return new DashboardPlugin(initializerContext); diff --git a/src/plugins/dashboard/public/mocks.tsx b/src/plugins/dashboard/public/mocks.tsx index ba30d72594f2a..07f29eca53042 100644 --- a/src/plugins/dashboard/public/mocks.tsx +++ b/src/plugins/dashboard/public/mocks.tsx @@ -20,6 +20,7 @@ import { DashboardStart } from './plugin'; export type Start = jest.Mocked; +export { mockAttributeService } from './attribute_service/attribute_service.mock'; const createStartContract = (): DashboardStart => { // @ts-ignore diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 5a45229a58a7d..eadb3cd207e4d 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -117,7 +117,7 @@ declare module '../../share/public' { export type DashboardUrlGenerator = UrlGeneratorContract; -interface DashboardFeatureFlagConfig { +export interface DashboardFeatureFlagConfig { allowByValueEmbeddables: boolean; } diff --git a/src/plugins/dashboard/public/url_utils/url_helper.test.ts b/src/plugins/dashboard/public/url_utils/url_helper.test.ts index 28d4ab032c33d..d2210e7380667 100644 --- a/src/plugins/dashboard/public/url_utils/url_helper.test.ts +++ b/src/plugins/dashboard/public/url_utils/url_helper.test.ts @@ -24,16 +24,17 @@ describe('', () => { const id = '123eb456cd'; const url = "/pep/app/dashboards#/create?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!())"; - expect(addEmbeddableToDashboardUrl(url, id, 'visualization')).toEqual( - `/pep/app/dashboards#/create?_a=%28description%3A%27%27%2Cfilters%3A%21%28%29%29&_g=%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29&addEmbeddableId=${id}&addEmbeddableType=visualization` + + expect(addEmbeddableToDashboardUrl(url, id, 'visualization')).toBe( + '/pep/app/dashboards?addEmbeddableId=123eb456cd&addEmbeddableType=visualization#%2Fcreate%3F_g%3D%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29%26_a%3D%28description%3A%27%27%2Cfilters%3A%21%28%29%29' ); }); it('addEmbeddableToDashboardUrl when dashboard is saved', () => { const id = '123eb456cd'; const url = "/pep/app/dashboards#/view/9b780cd0-3dd3-11e8-b2b9-5d5dc1715159?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!())"; - expect(addEmbeddableToDashboardUrl(url, id, 'visualization')).toEqual( - `/pep/app/dashboards#/view/9b780cd0-3dd3-11e8-b2b9-5d5dc1715159?_a=%28description%3A%27%27%2Cfilters%3A%21%28%29%29&_g=%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29&addEmbeddableId=${id}&addEmbeddableType=visualization` + expect(addEmbeddableToDashboardUrl(url, id, 'visualization')).toBe( + '/pep/app/dashboards?addEmbeddableId=123eb456cd&addEmbeddableType=visualization#%2Fview%2F9b780cd0-3dd3-11e8-b2b9-5d5dc1715159%3F_g%3D%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29%26_a%3D%28description%3A%27%27%2Cfilters%3A%21%28%29%29' ); }); }); diff --git a/src/plugins/dashboard/public/url_utils/url_helper.ts b/src/plugins/dashboard/public/url_utils/url_helper.ts index 61737e81cf24d..1f4706f0b8a4d 100644 --- a/src/plugins/dashboard/public/url_utils/url_helper.ts +++ b/src/plugins/dashboard/public/url_utils/url_helper.ts @@ -17,7 +17,7 @@ * under the License. */ -import { parseUrl, stringify } from 'query-string'; +import { parseUrl, stringifyUrl } from 'query-string'; import { DashboardConstants } from '../index'; /** * @@ -34,12 +34,14 @@ export function addEmbeddableToDashboardUrl( embeddableId: string, embeddableType: string ) { - const { url, query } = parseUrl(dashboardUrl); + const { url, query, fragmentIdentifier } = parseUrl(dashboardUrl, { + parseFragmentIdentifier: true, + }); if (embeddableId) { query[DashboardConstants.ADD_EMBEDDABLE_TYPE] = embeddableType; query[DashboardConstants.ADD_EMBEDDABLE_ID] = embeddableId; } - return `${url}?${stringify(query)}`; + return stringifyUrl({ url, query, fragmentIdentifier }); } diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index bc7080e7d450b..153b6a633b66d 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -27,3 +27,10 @@ export * from './query'; export * from './search'; export * from './types'; export * from './utils'; + +/** + * Use data plugin interface instead + * @deprecated + */ + +export { IndexPatternAttributes } from './types'; diff --git a/src/plugins/data/common/index_patterns/errors.ts b/src/plugins/data/common/index_patterns/errors.ts deleted file mode 100644 index 3d92bae1968fb..0000000000000 --- a/src/plugins/data/common/index_patterns/errors.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { FieldSpec } from './types'; - -export class FieldTypeUnknownError extends Error { - public readonly fieldSpec: FieldSpec; - constructor(message: string, spec: FieldSpec) { - super(message); - this.name = 'FieldTypeUnknownError'; - this.fieldSpec = spec; - } -} diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js b/src/plugins/data/common/index_patterns/errors/duplicate_index_pattern.ts similarity index 83% rename from src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js rename to src/plugins/data/common/index_patterns/errors/duplicate_index_pattern.ts index 0eef126f2255a..c42dcc1c6a24d 100644 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js +++ b/src/plugins/data/common/index_patterns/errors/duplicate_index_pattern.ts @@ -17,13 +17,9 @@ * under the License. */ -export default function (kibana) { - return [ - new kibana.Plugin({ - id: 'bar:one', - }), - new kibana.Plugin({ - id: 'bar:two', - }), - ]; +export class DuplicateIndexPatternError extends Error { + constructor(message: string) { + super(message); + this.name = 'DuplicateIndexPatternError'; + } } diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/index.js b/src/plugins/data/common/index_patterns/errors/index.ts similarity index 94% rename from src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/index.js rename to src/plugins/data/common/index_patterns/errors/index.ts index 59f4a2649f019..7cc39d93a2a18 100644 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/index.js +++ b/src/plugins/data/common/index_patterns/errors/index.ts @@ -17,6 +17,4 @@ * under the License. */ -export default { - foo: 'bar', -}; +export * from './duplicate_index_pattern'; diff --git a/src/plugins/data/common/index_patterns/fields/field_list.ts b/src/plugins/data/common/index_patterns/fields/field_list.ts index 4cf6075869851..c0eb55a15fead 100644 --- a/src/plugins/data/common/index_patterns/fields/field_list.ts +++ b/src/plugins/data/common/index_patterns/fields/field_list.ts @@ -20,7 +20,7 @@ import { findIndex } from 'lodash'; import { IFieldType } from './types'; import { IndexPatternField } from './index_pattern_field'; -import { OnNotification, FieldSpec } from '../types'; +import { FieldSpec, IndexPatternFieldMap } from '../types'; import { IndexPattern } from '../index_patterns'; import { shortenDottedString } from '../../utils'; @@ -35,16 +35,11 @@ export interface IIndexPatternFieldList extends Array { removeAll(): void; replaceAll(specs: FieldSpec[]): void; update(field: FieldSpec): void; - toSpec(options?: { getFormatterForField?: IndexPattern['getFormatterForField'] }): FieldSpec[]; + toSpec(options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }): IndexPatternFieldMap; } -export type CreateIndexPatternFieldList = ( - indexPattern: IndexPattern, - specs?: FieldSpec[], - shortDotsEnable?: boolean, - onNotification?: OnNotification -) => IIndexPatternFieldList; - // extending the array class and using a constructor doesn't work well // when calling filter and similar so wrapping in a callback. // to be removed in the future @@ -105,7 +100,7 @@ export const fieldList = ( this.groups.clear(); }; - public readonly replaceAll = (spcs: FieldSpec[]) => { + public readonly replaceAll = (spcs: FieldSpec[] = []) => { this.removeAll(); spcs.forEach(this.add); }; @@ -115,7 +110,12 @@ export const fieldList = ( }: { getFormatterForField?: IndexPattern['getFormatterForField']; } = {}) { - return [...this.map((field) => field.toSpec({ getFormatterForField }))]; + return { + ...this.reduce((collector, field) => { + collector[field.name] = field.toSpec({ getFormatterForField }); + return collector; + }, {}), + }; } } diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts index 7f72bfe55c7cd..808afc3449c2a 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts @@ -17,12 +17,9 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; import { KbnFieldType, getKbnFieldType } from '../../kbn_field_types'; -import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { IFieldType } from './types'; import { FieldSpec, IndexPattern } from '../..'; -import { FieldTypeUnknownError } from '../errors'; export class IndexPatternField implements IFieldType { readonly spec: FieldSpec; @@ -35,16 +32,12 @@ export class IndexPatternField implements IFieldType { this.displayName = displayName; this.kbnFieldType = getKbnFieldType(spec.type); - if (spec.type && this.kbnFieldType?.name === KBN_FIELD_TYPES.UNKNOWN) { - const msg = i18n.translate('data.indexPatterns.unknownFieldTypeErrorMsg', { - values: { type: spec.type, name: spec.name }, - defaultMessage: `Field '{name}' Unknown field type '{type}'`, - }); - throw new FieldTypeUnknownError(msg, spec); - } } // writable attrs + /** + * Count is used for field popularity + */ public get count() { return this.spec.count || 0; } @@ -53,6 +46,9 @@ export class IndexPatternField implements IFieldType { this.spec.count = count; } + /** + * Script field code + */ public get script() { return this.spec.script; } @@ -61,6 +57,9 @@ export class IndexPatternField implements IFieldType { this.spec.script = script; } + /** + * Script field language + */ public get lang() { return this.spec.lang; } @@ -69,6 +68,9 @@ export class IndexPatternField implements IFieldType { this.spec.lang = lang; } + /** + * Description of field type conflicts across different indices in the same index pattern + */ public get conflictDescriptions() { return this.spec.conflictDescriptions; } @@ -152,7 +154,7 @@ export class IndexPatternField implements IFieldType { getFormatterForField, }: { getFormatterForField?: IndexPattern['getFormatterForField']; - } = {}) { + } = {}): FieldSpec { return { count: this.count, script: this.script, diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap index 1871627da76de..ed84aceb60e5a 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap @@ -2,13 +2,13 @@ exports[`IndexPattern toSpec should match snapshot 1`] = ` Object { - "fields": Array [ - Object { + "fields": Object { + "@tags": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 10, + "count": 0, "esTypes": Array [ - "long", + "keyword", ], "format": Object { "id": "number", @@ -17,20 +17,20 @@ Object { }, }, "lang": undefined, - "name": "bytes", + "name": "@tags", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "number", + "type": "string", }, - Object { + "@timestamp": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 20, + "count": 30, "esTypes": Array [ - "boolean", + "date", ], "format": Object { "id": "number", @@ -39,20 +39,20 @@ Object { }, }, "lang": undefined, - "name": "ssl", + "name": "@timestamp", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "boolean", + "type": "date", }, - Object { + "_id": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 30, + "count": 0, "esTypes": Array [ - "date", + "_id", ], "format": Object { "id": "number", @@ -61,20 +61,20 @@ Object { }, }, "lang": undefined, - "name": "@timestamp", - "readFromDocValues": true, + "name": "_id", + "readFromDocValues": false, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "date", + "type": "string", }, - Object { + "_source": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 30, + "count": 0, "esTypes": Array [ - "date", + "_source", ], "format": Object { "id": "number", @@ -83,20 +83,20 @@ Object { }, }, "lang": undefined, - "name": "time", - "readFromDocValues": true, + "name": "_source", + "readFromDocValues": false, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "date", + "type": "_source", }, - Object { + "_type": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "keyword", + "_type", ], "format": Object { "id": "number", @@ -105,20 +105,20 @@ Object { }, }, "lang": undefined, - "name": "@tags", - "readFromDocValues": true, + "name": "_type", + "readFromDocValues": false, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, "type": "string", }, - Object { + "area": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "date", + "geo_shape", ], "format": Object { "id": "number", @@ -127,20 +127,20 @@ Object { }, }, "lang": undefined, - "name": "utc_time", - "readFromDocValues": true, + "name": "area", + "readFromDocValues": false, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "date", + "type": "geo_shape", }, - Object { + "bytes": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 0, + "count": 10, "esTypes": Array [ - "integer", + "long", ], "format": Object { "id": "number", @@ -149,7 +149,7 @@ Object { }, }, "lang": undefined, - "name": "phpmemory", + "name": "bytes", "readFromDocValues": true, "script": undefined, "scripted": false, @@ -157,12 +157,12 @@ Object { "subType": undefined, "type": "number", }, - Object { + "custom_user_field": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "ip", + "conflict", ], "format": Object { "id": "number", @@ -171,20 +171,20 @@ Object { }, }, "lang": undefined, - "name": "ip", + "name": "custom_user_field", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "ip", + "type": "conflict", }, - Object { + "extension": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "attachment", + "text", ], "format": Object { "id": "number", @@ -193,15 +193,41 @@ Object { }, }, "lang": undefined, - "name": "request_body", - "readFromDocValues": true, + "name": "extension", + "readFromDocValues": false, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "attachment", + "type": "string", }, - Object { + "extension.keyword": Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "esTypes": Array [ + "keyword", + ], + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, + "lang": undefined, + "name": "extension.keyword", + "readFromDocValues": true, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": Object { + "multi": Object { + "parent": "extension", + }, + }, + "type": "string", + }, + "geo.coordinates": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, @@ -215,7 +241,7 @@ Object { }, }, "lang": undefined, - "name": "point", + "name": "geo.coordinates", "readFromDocValues": true, "script": undefined, "scripted": false, @@ -223,12 +249,12 @@ Object { "subType": undefined, "type": "geo_point", }, - Object { + "geo.src": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "geo_shape", + "keyword", ], "format": Object { "id": "number", @@ -237,15 +263,15 @@ Object { }, }, "lang": undefined, - "name": "area", - "readFromDocValues": false, + "name": "geo.src", + "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "geo_shape", + "type": "string", }, - Object { + "hashed": Object { "aggregatable": false, "conflictDescriptions": undefined, "count": 0, @@ -267,12 +293,12 @@ Object { "subType": undefined, "type": "murmur3", }, - Object { + "ip": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "geo_point", + "ip", ], "format": Object { "id": "number", @@ -281,15 +307,15 @@ Object { }, }, "lang": undefined, - "name": "geo.coordinates", + "name": "ip", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "geo_point", + "type": "ip", }, - Object { + "machine.os": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, @@ -303,7 +329,7 @@ Object { }, }, "lang": undefined, - "name": "extension", + "name": "machine.os", "readFromDocValues": false, "script": undefined, "scripted": false, @@ -311,7 +337,7 @@ Object { "subType": undefined, "type": "string", }, - Object { + "machine.os.raw": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, @@ -325,19 +351,19 @@ Object { }, }, "lang": undefined, - "name": "extension.keyword", + "name": "machine.os.raw", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": Object { "multi": Object { - "parent": "extension", + "parent": "machine.os", }, }, "type": "string", }, - Object { + "non-filterable": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, @@ -351,20 +377,20 @@ Object { }, }, "lang": undefined, - "name": "machine.os", + "name": "non-filterable", "readFromDocValues": false, "script": undefined, "scripted": false, - "searchable": true, + "searchable": false, "subType": undefined, "type": "string", }, - Object { - "aggregatable": true, + "non-sortable": Object { + "aggregatable": false, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "keyword", + "text", ], "format": Object { "id": "number", @@ -373,24 +399,20 @@ Object { }, }, "lang": undefined, - "name": "machine.os.raw", - "readFromDocValues": true, + "name": "non-sortable", + "readFromDocValues": false, "script": undefined, "scripted": false, - "searchable": true, - "subType": Object { - "multi": Object { - "parent": "machine.os", - }, - }, + "searchable": false, + "subType": undefined, "type": "string", }, - Object { + "phpmemory": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "keyword", + "integer", ], "format": Object { "id": "number", @@ -399,20 +421,20 @@ Object { }, }, "lang": undefined, - "name": "geo.src", + "name": "phpmemory", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "string", + "type": "number", }, - Object { + "point": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "_id", + "geo_point", ], "format": Object { "id": "number", @@ -421,20 +443,20 @@ Object { }, }, "lang": undefined, - "name": "_id", - "readFromDocValues": false, + "name": "point", + "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "string", + "type": "geo_point", }, - Object { + "request_body": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "_type", + "attachment", ], "format": Object { "id": "number", @@ -443,20 +465,20 @@ Object { }, }, "lang": undefined, - "name": "_type", - "readFromDocValues": false, + "name": "request_body", + "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "string", + "type": "attachment", }, - Object { + "script date": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "_source", + "date", ], "format": Object { "id": "number", @@ -464,43 +486,21 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": undefined, - "name": "_source", + "lang": "painless", + "name": "script date", "readFromDocValues": false, - "script": undefined, - "scripted": false, + "script": "1234", + "scripted": true, "searchable": true, "subType": undefined, - "type": "_source", + "type": "date", }, - Object { + "script murmur3": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "text", - ], - "format": Object { - "id": "number", - "params": Object { - "pattern": "$0,0.[00]", - }, - }, - "lang": undefined, - "name": "non-filterable", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": false, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": false, - "conflictDescriptions": undefined, - "count": 0, - "esTypes": Array [ - "text", + "murmur3", ], "format": Object { "id": "number", @@ -508,21 +508,21 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": undefined, - "name": "non-sortable", + "lang": "expression", + "name": "script murmur3", "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": false, + "script": "1234", + "scripted": true, + "searchable": true, "subType": undefined, - "type": "string", + "type": "murmur3", }, - Object { + "script number": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "conflict", + "long", ], "format": Object { "id": "number", @@ -530,16 +530,16 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": undefined, - "name": "custom_user_field", - "readFromDocValues": true, - "script": undefined, - "scripted": false, + "lang": "expression", + "name": "script number", + "readFromDocValues": false, + "script": "1234", + "scripted": true, "searchable": true, "subType": undefined, - "type": "conflict", + "type": "number", }, - Object { + "script string": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, @@ -561,12 +561,12 @@ Object { "subType": undefined, "type": "string", }, - Object { + "ssl": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 0, + "count": 20, "esTypes": Array [ - "long", + "boolean", ], "format": Object { "id": "number", @@ -574,19 +574,19 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": "expression", - "name": "script number", - "readFromDocValues": false, - "script": "1234", - "scripted": true, + "lang": undefined, + "name": "ssl", + "readFromDocValues": true, + "script": undefined, + "scripted": false, "searchable": true, "subType": undefined, - "type": "number", + "type": "boolean", }, - Object { + "time": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 0, + "count": 30, "esTypes": Array [ "date", ], @@ -596,21 +596,21 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": "painless", - "name": "script date", - "readFromDocValues": false, - "script": "1234", - "scripted": true, + "lang": undefined, + "name": "time", + "readFromDocValues": true, + "script": undefined, + "scripted": false, "searchable": true, "subType": undefined, "type": "date", }, - Object { + "utc_time": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "murmur3", + "date", ], "format": Object { "id": "number", @@ -618,21 +618,22 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": "expression", - "name": "script murmur3", - "readFromDocValues": false, - "script": "1234", - "scripted": true, + "lang": undefined, + "name": "utc_time", + "readFromDocValues": true, + "script": undefined, + "scripted": false, "searchable": true, "subType": undefined, - "type": "murmur3", + "type": "date", }, - ], + }, "id": "test-pattern", "sourceFilters": undefined, "timeFieldName": "timestamp", "title": "title", + "type": "index-pattern", "typeMeta": undefined, - "version": 2, + "version": "2", } `; diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap new file mode 100644 index 0000000000000..752fdcf11991c --- /dev/null +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`IndexPatterns savedObjectToSpec 1`] = ` +Object { + "fields": Object {}, + "id": "id", + "intervalName": undefined, + "sourceFilters": Array [ + Object { + "value": "item1", + }, + Object { + "value": "item2", + }, + ], + "timeFieldName": "@timestamp", + "title": "kibana-*", + "type": "", + "typeMeta": Object {}, + "version": "version", +} +`; diff --git a/src/plugins/data/common/index_patterns/index_patterns/_fields_fetcher.ts b/src/plugins/data/common/index_patterns/index_patterns/_fields_fetcher.ts deleted file mode 100644 index 4eba0576ff235..0000000000000 --- a/src/plugins/data/common/index_patterns/index_patterns/_fields_fetcher.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IndexPattern } from '.'; -import { GetFieldsOptions, IIndexPatternsApiClient } from '../types'; - -/** @internal */ -export const createFieldsFetcher = ( - indexPattern: IndexPattern, - apiClient: IIndexPatternsApiClient, - metaFields: string[] = [] -) => { - const fieldFetcher = { - fetch: (options: GetFieldsOptions) => { - return fieldFetcher.fetchForWildcard(indexPattern.title, { - ...options, - type: indexPattern.type, - params: indexPattern.typeMeta && indexPattern.typeMeta.params, - }); - }, - fetchForWildcard: (pattern: string, options: GetFieldsOptions = {}) => { - return apiClient.getFieldsForWildcard({ - pattern, - metaFields, - type: options.type, - params: options.params || {}, - }); - }, - }; - - return fieldFetcher; -}; diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts index f49897c47d562..91286a38f16a0 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts @@ -17,19 +17,18 @@ * under the License. */ -import { defaults, map, last } from 'lodash'; +import { map, last } from 'lodash'; import { IndexPattern } from './index_pattern'; import { DuplicateField } from '../../../../kibana_utils/common'; -// @ts-ignore +// @ts-expect-error import mockLogStashFields from '../../../../../fixtures/logstash_fields'; -// @ts-ignore import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_saved_object_index_pattern'; import { IndexPatternField } from '../fields'; import { fieldFormatsMock } from '../../field_formats/mocks'; -import { FieldFormat, IndexPatternsService } from '../..'; +import { FieldFormat } from '../..'; class MockFieldFormatter {} @@ -63,90 +62,33 @@ jest.mock('../../field_mapping', () => { }; }); -let mockFieldsFetcherResponse: any[] = []; - -jest.mock('./_fields_fetcher', () => ({ - createFieldsFetcher: jest.fn().mockImplementation(() => ({ - fetch: jest.fn().mockImplementation(() => { - return new Promise((resolve) => resolve(mockFieldsFetcherResponse)); - }), - every: jest.fn(), - })), -})); - -let object: any = {}; - -const savedObjectsClient = { - create: jest.fn(), - get: jest.fn().mockImplementation(() => object), - update: jest.fn().mockImplementation(async (type, id, body, { version }) => { - if (object.version !== version) { - throw new Object({ - res: { - status: 409, - }, - }); - } - object.attributes.title = body.title; - object.version += 'a'; - return { - id: object.id, - version: object.version, - }; - }), -}; - -const patternCache = { - clear: jest.fn(), - get: jest.fn(), - set: jest.fn(), - clearAll: jest.fn(), -}; - -const apiClient = { - _getUrl: jest.fn(), - getFieldsForTimePattern: jest.fn(), - getFieldsForWildcard: jest.fn(), -}; - // helper function to create index patterns -function create(id: string, payload?: any): Promise { - const indexPattern = new IndexPattern(id, { - savedObjectsClient: savedObjectsClient as any, - apiClient, - patternCache, +function create(id: string) { + const { + type, + version, + attributes: { timeFieldName, fields, title }, + } = stubbedSavedObjectIndexPattern(id); + + return new IndexPattern({ + spec: { id, type, version, timeFieldName, fields, title }, + savedObjectsClient: {} as any, fieldFormats: fieldFormatsMock, - indexPatternsService: {} as IndexPatternsService, - onNotification: () => {}, - onError: () => {}, shortDotsEnable: false, metaFields: [], }); - - setDocsourcePayload(id, payload); - - return indexPattern.init(); -} - -function setDocsourcePayload(id: string | null, providedPayload: any) { - object = defaults(providedPayload || {}, stubbedSavedObjectIndexPattern(id)); } describe('IndexPattern', () => { - const indexPatternId = 'test-pattern'; - let indexPattern: IndexPattern; // create an indexPattern instance for each test beforeEach(() => { - return create(indexPatternId).then((pattern: IndexPattern) => { - indexPattern = pattern; - }); + indexPattern = create('test-pattern'); }); describe('api', () => { test('should have expected properties', () => { - expect(indexPattern).toHaveProperty('refreshFields'); expect(indexPattern).toHaveProperty('popularizeField'); expect(indexPattern).toHaveProperty('getScriptedFields'); expect(indexPattern).toHaveProperty('getNonScriptedFields'); @@ -158,13 +100,6 @@ describe('IndexPattern', () => { }); }); - describe('init', () => { - test('should append the found fields', () => { - expect(savedObjectsClient.get).toHaveBeenCalled(); - expect(indexPattern.fields).toHaveLength(mockLogStashFields().length); - }); - }); - describe('fields', () => { test('should have expected properties on fields', function () { expect(indexPattern.fields[0]).toHaveProperty('displayName'); @@ -229,43 +164,9 @@ describe('IndexPattern', () => { }); }); - describe('refresh fields', () => { - test('should fetch fields from the fieldsFetcher', async () => { - expect(indexPattern.fields.length).toBeGreaterThan(2); - - mockFieldsFetcherResponse = [{ name: 'foo' }, { name: 'bar' }]; - - await indexPattern.refreshFields(); - - mockFieldsFetcherResponse = []; - - const newFields = indexPattern.getNonScriptedFields(); - - expect(newFields).toHaveLength(2); - expect([...newFields.map((f) => f.name)]).toEqual(['foo', 'bar']); - }); - - test('should preserve the scripted fields', async () => { - // add spy to indexPattern.getScriptedFields - // sinon.spy(indexPattern, 'getScriptedFields'); - - // refresh fields, which will fetch - await indexPattern.refreshFields(); - - // called to append scripted fields to the response from mapper.getFieldsForIndexPattern - // sinon.assert.calledOnce(indexPattern.getScriptedFields); - expect(indexPattern.getScriptedFields().map((f) => f.name)).toEqual( - mockLogStashFields() - .filter((f: IndexPatternField) => f.scripted) - .map((f: IndexPatternField) => f.name) - ); - }); - }); - describe('add and remove scripted fields', () => { test('should append the scripted field', async () => { // keep a copy of the current scripted field count - // const saveSpy = sinon.spy(indexPattern, 'save'); const oldCount = indexPattern.getScriptedFields().length; // add a new scripted field @@ -283,7 +184,6 @@ describe('IndexPattern', () => { ); const scriptedFields = indexPattern.getScriptedFields(); - // expect(saveSpy.callCount).to.equal(1); expect(scriptedFields).toHaveLength(oldCount + 1); expect((indexPattern.fields.getByName(scriptedField.name) as IndexPatternField).name).toEqual( scriptedField.name @@ -291,14 +191,12 @@ describe('IndexPattern', () => { }); test('should remove scripted field, by name', async () => { - // const saveSpy = sinon.spy(indexPattern, 'save'); const scriptedFields = indexPattern.getScriptedFields(); const oldCount = scriptedFields.length; const scriptedField = last(scriptedFields)!; await indexPattern.removeScriptedField(scriptedField.name); - // expect(saveSpy.callCount).to.equal(1); expect(indexPattern.getScriptedFields().length).toEqual(oldCount - 1); expect(indexPattern.fields.getByName(scriptedField.name)).toEqual(undefined); }); @@ -330,8 +228,13 @@ describe('IndexPattern', () => { } as FieldFormat; indexPattern.getFormatterForField = () => formatter; const spec = indexPattern.toSpec(); - const restoredPattern = await create(spec.id as string); - restoredPattern.initFromSpec(spec); + const restoredPattern = new IndexPattern({ + spec, + savedObjectsClient: {} as any, + fieldFormats: fieldFormatsMock, + shortDotsEnable: false, + metaFields: [], + }); expect(restoredPattern.id).toEqual(indexPattern.id); expect(restoredPattern.title).toEqual(indexPattern.title); expect(restoredPattern.timeFieldName).toEqual(indexPattern.timeFieldName); @@ -342,26 +245,22 @@ describe('IndexPattern', () => { describe('popularizeField', () => { test('should increment the popularity count by default', () => { - // const saveSpy = sinon.stub(indexPattern, 'save'); indexPattern.fields.forEach(async (field) => { const oldCount = field.count || 0; await indexPattern.popularizeField(field.name); - // expect(saveSpy.callCount).to.equal(i + 1); expect(field.count).toEqual(oldCount + 1); }); }); test('should increment the popularity count', () => { - // const saveSpy = sinon.stub(indexPattern, 'save'); indexPattern.fields.forEach(async (field) => { const oldCount = field.count || 0; const incrementAmount = 4; await indexPattern.popularizeField(field.name, incrementAmount); - // expect(saveSpy.callCount).to.equal(i + 1); expect(field.count).toEqual(oldCount + incrementAmount); }); }); diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index 76f1a5e59d0ee..882235889b55c 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -18,211 +18,94 @@ */ import _, { each, reject } from 'lodash'; -import { i18n } from '@kbn/i18n'; import { SavedObjectsClientCommon } from '../..'; -import { DuplicateField, SavedObjectNotFound } from '../../../../kibana_utils/common'; +import { DuplicateField } from '../../../../kibana_utils/common'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, - FieldTypeUnknownError, FieldFormatNotFoundError, + IFieldType, } from '../../../common'; -import { findByTitle } from '../utils'; -import { IndexPatternMissingIndices } from '../lib'; import { IndexPatternField, IIndexPatternFieldList, fieldList } from '../fields'; -import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; -import { OnNotification, OnError, IIndexPatternsApiClient, IndexPatternAttributes } from '../types'; import { FieldFormatsStartCommon, FieldFormat } from '../../field_formats'; -import { PatternCache } from './_pattern_cache'; -import { expandShorthand, FieldMappingSpec, MappingObject } from '../../field_mapping'; -import { IndexPatternSpec, TypeMeta, FieldSpec, SourceFilter } from '../types'; +import { IndexPatternSpec, TypeMeta, SourceFilter, IndexPatternFieldMap } from '../types'; import { SerializedFieldFormat } from '../../../../expressions/common'; -import { IndexPatternsService } from '..'; - -const savedObjectType = 'index-pattern'; interface IndexPatternDeps { + spec?: IndexPatternSpec; savedObjectsClient: SavedObjectsClientCommon; - apiClient: IIndexPatternsApiClient; - patternCache: PatternCache; fieldFormats: FieldFormatsStartCommon; - indexPatternsService: IndexPatternsService; - onNotification: OnNotification; - onError: OnError; shortDotsEnable: boolean; metaFields: string[]; } +interface SavedObjectBody { + title?: string; + timeFieldName?: string; + intervalName?: string; + fields?: string; + sourceFilters?: string; + fieldFormatMap?: string; + typeMeta?: string; + type?: string; +} + +type FormatFieldFn = (hit: Record, fieldName: string) => any; + export class IndexPattern implements IIndexPattern { public id?: string; public title: string = ''; - public fieldFormatMap: any; + public fieldFormatMap: Record; public typeMeta?: TypeMeta; - public fields: IIndexPatternFieldList & { toSpec: () => FieldSpec[] }; + public fields: IIndexPatternFieldList & { toSpec: () => IndexPatternFieldMap }; public timeFieldName: string | undefined; public intervalName: string | undefined; public type: string | undefined; - public formatHit: any; - public formatField: any; - public flattenHit: any; + public formatHit: { + (hit: Record, type?: string): any; + formatField: FormatFieldFn; + }; + public formatField: FormatFieldFn; + public flattenHit: (hit: Record, deep?: boolean) => Record; public metaFields: string[]; - + // savedObject version public version: string | undefined; private savedObjectsClient: SavedObjectsClientCommon; - private patternCache: PatternCache; public sourceFilters?: SourceFilter[]; - // todo make read only, update via method or factor out - public originalBody: { [key: string]: any } = {}; - public fieldsFetcher: any; // probably want to factor out any direct usage and change to private - private indexPatternsService: IndexPatternsService; + private originalSavedObjectBody: SavedObjectBody = {}; private shortDotsEnable: boolean = false; private fieldFormats: FieldFormatsStartCommon; - private onNotification: OnNotification; - private onError: OnError; - - private mapping: MappingObject = expandShorthand({ - title: ES_FIELD_TYPES.TEXT, - timeFieldName: ES_FIELD_TYPES.KEYWORD, - intervalName: ES_FIELD_TYPES.KEYWORD, - fields: 'json', - sourceFilters: 'json', - fieldFormatMap: { - type: ES_FIELD_TYPES.TEXT, - _serialize: (map = {}) => { - const serialized = _.transform(map, this.serializeFieldFormatMap); - return _.isEmpty(serialized) ? undefined : JSON.stringify(serialized); - }, - _deserialize: (map = '{}') => { - return _.mapValues(JSON.parse(map), (mapping) => { - return this.deserializeFieldFormatMap(mapping); - }); - }, - }, - type: ES_FIELD_TYPES.KEYWORD, - typeMeta: 'json', - }); - - constructor( - id: string | undefined, - { - savedObjectsClient, - apiClient, - patternCache, - fieldFormats, - indexPatternsService, - onNotification, - onError, - shortDotsEnable = false, - metaFields = [], - }: IndexPatternDeps - ) { - this.id = id; + + constructor({ + spec = {}, + savedObjectsClient, + fieldFormats, + shortDotsEnable = false, + metaFields = [], + }: IndexPatternDeps) { + // set dependencies this.savedObjectsClient = savedObjectsClient; - this.patternCache = patternCache; this.fieldFormats = fieldFormats; - this.indexPatternsService = indexPatternsService; - this.onNotification = onNotification; - this.onError = onError; - + // set config this.shortDotsEnable = shortDotsEnable; this.metaFields = metaFields; + // initialize functionality this.fields = fieldList([], this.shortDotsEnable); - this.fieldsFetcher = createFieldsFetcher(this, apiClient, metaFields); this.flattenHit = flattenHitWrapper(this, metaFields); this.formatHit = formatHitProvider( this, fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING) ); this.formatField = this.formatHit.formatField; - } - - private unknownFieldErrorNotification( - fieldType: string, - fieldName: string, - indexPatternTitle: string - ) { - const title = i18n.translate('data.indexPatterns.unknownFieldHeader', { - values: { type: fieldType }, - defaultMessage: 'Unknown field type {type}', - }); - const text = i18n.translate('data.indexPatterns.unknownFieldErrorMessage', { - values: { name: fieldName, title: indexPatternTitle }, - defaultMessage: 'Field {name} in indexPattern {title} is using an unknown field type.', - }); - this.onNotification({ title, text, color: 'danger', iconType: 'alert' }); - } - - private serializeFieldFormatMap(flat: any, format: string, field: string | undefined) { - if (format && field) { - flat[field] = format; - } - } - - private deserializeFieldFormatMap(mapping: any) { - try { - return this.fieldFormats.getInstance(mapping.id, mapping.params); - } catch (err) { - if (err instanceof FieldFormatNotFoundError) { - return undefined; - } else { - throw err; - } - } - } - - private isFieldRefreshRequired(specs?: FieldSpec[]): boolean { - if (!specs) { - return true; - } - - return specs.every((spec) => { - // See https://github.com/elastic/kibana/pull/8421 - const hasFieldCaps = 'aggregatable' in spec && 'searchable' in spec; - - // See https://github.com/elastic/kibana/pull/11969 - const hasDocValuesFlag = 'readFromDocValues' in spec; - - return !hasFieldCaps || !hasDocValuesFlag; - }); - } - - private async indexFields(specs?: FieldSpec[]) { - if (!this.id) { - return; - } - - if (this.isFieldRefreshRequired(specs)) { - await this.refreshFields(); - } else { - if (specs) { - try { - this.fields.replaceAll(specs); - } catch (err) { - if (err instanceof FieldTypeUnknownError) { - this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); - } else { - throw err; - } - } - } - } - } - public initFromSpec(spec: IndexPatternSpec) { - // create fieldFormatMap from field list - const fieldFormatMap: Record = {}; - if (_.isArray(spec.fields)) { - spec.fields.forEach((field: FieldSpec) => { - if (field.format) { - fieldFormatMap[field.name as string] = { ...field.format }; - } - }); - } + // set values + this.id = spec.id; + const fieldFormatMap = this.fieldSpecsToFieldFormatMap(spec.fields); this.version = spec.version; @@ -230,53 +113,55 @@ export class IndexPattern implements IIndexPattern { this.timeFieldName = spec.timeFieldName; this.sourceFilters = spec.sourceFilters; - try { - this.fields.replaceAll(spec.fields || []); - } catch (err) { - if (err instanceof FieldTypeUnknownError) { - this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); - } else { - throw err; - } - } + this.fields.replaceAll(Object.values(spec.fields || {})); + this.type = spec.type; this.typeMeta = spec.typeMeta; this.fieldFormatMap = _.mapValues(fieldFormatMap, (mapping) => { return this.deserializeFieldFormatMap(mapping); }); - - return this; } - private updateFromElasticSearch(response: any) { - if (!response.found) { - throw new SavedObjectNotFound(savedObjectType, this.id, 'management/kibana/indexPatterns'); - } - - _.forOwn(this.mapping, (fieldMapping: FieldMappingSpec, name: string | undefined) => { - if (!fieldMapping._deserialize || !name) { - return; + /** + * Get last saved saved object fields + */ + getOriginalSavedObjectBody = () => ({ ...this.originalSavedObjectBody }); + + /** + * Reset last saved saved object fields. used after saving + */ + resetOriginalSavedObjectBody = () => { + this.originalSavedObjectBody = this.getAsSavedObjectBody(); + }; + + /** + * Converts field format spec to field format instance + * @param mapping + */ + private deserializeFieldFormatMap(mapping: SerializedFieldFormat>) { + try { + return this.fieldFormats.getInstance(mapping.id as string, mapping.params); + } catch (err) { + if (err instanceof FieldFormatNotFoundError) { + return undefined; + } else { + throw err; } - - response[name] = fieldMapping._deserialize(response[name]); - }); - - this.title = response.title; - this.timeFieldName = response.timeFieldName; - this.intervalName = response.intervalName; - this.sourceFilters = response.sourceFilters; - this.fieldFormatMap = response.fieldFormatMap; - this.type = response.type; - this.typeMeta = response.typeMeta; - - if (!this.title && this.id) { - this.title = this.id; } - this.version = response.version; - - return this.indexFields(response.fields); } + /** + * Extracts FieldFormatMap from FieldSpec map + * @param fldList FieldSpec map + */ + private fieldSpecsToFieldFormatMap = (fldList: IndexPatternSpec['fields'] = {}) => + Object.values(fldList).reduce>((col, fieldSpec) => { + if (fieldSpec.format) { + col[fieldSpec.name] = { ...fieldSpec.format }; + } + return col; + }, {}); + getComputedFields() { const scriptFields: any = {}; if (!this.fields) { @@ -318,37 +203,6 @@ export class IndexPattern implements IIndexPattern { }; } - async init() { - if (!this.id) { - return this; // no id === no elasticsearch document - } - - const savedObject = await this.savedObjectsClient.get( - savedObjectType, - this.id - ); - - const response = { - version: savedObject.version, - found: savedObject.version ? true : false, - title: savedObject.attributes.title, - timeFieldName: savedObject.attributes.timeFieldName, - intervalName: savedObject.attributes.intervalName, - fields: savedObject.attributes.fields, - sourceFilters: savedObject.attributes.sourceFilters, - fieldFormatMap: savedObject.attributes.fieldFormatMap, - typeMeta: savedObject.attributes.typeMeta, - type: savedObject.attributes.type, - }; - // Do this before we attempt to update from ES since that call can potentially perform a save - this.originalBody = this.prepBody(); - await this.updateFromElasticSearch(response); - // Do it after to ensure we have the most up to date information - this.originalBody = this.prepBody(); - - return this; - } - public toSpec(): IndexPatternSpec { return { id: this.id, @@ -359,17 +213,33 @@ export class IndexPattern implements IIndexPattern { sourceFilters: this.sourceFilters, fields: this.fields.toSpec({ getFormatterForField: this.getFormatterForField.bind(this) }), typeMeta: this.typeMeta, + type: this.type, }; } - // Get the source filtering configuration for that index. + /** + * Get the source filtering configuration for that index. + */ getSourceFiltering() { return { excludes: (this.sourceFilters && this.sourceFilters.map((filter: any) => filter.value)) || [], }; } - async addScriptedField(name: string, script: string, fieldType: string = 'string', lang: string) { + /** + * Add scripted field to field list + * + * @param name field name + * @param script script code + * @param fieldType + * @param lang + */ + async addScriptedField( + name: string, + script: string, + fieldType: string = 'string', + lang: string = 'painless' + ) { const scriptedFields = this.getScriptedFields(); const names = _.map(scriptedFields, 'name'); @@ -377,27 +247,24 @@ export class IndexPattern implements IIndexPattern { throw new DuplicateField(name); } - try { - this.fields.add({ - name, - script, - type: fieldType, - scripted: true, - lang, - aggregatable: true, - searchable: true, - count: 0, - readFromDocValues: false, - }); - } catch (err) { - if (err instanceof FieldTypeUnknownError) { - this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); - } else { - throw err; - } - } + this.fields.add({ + name, + script, + type: fieldType, + scripted: true, + lang, + aggregatable: true, + searchable: true, + count: 0, + readFromDocValues: false, + }); } + /** + * Remove scripted field from field list + * @param fieldName + */ + removeScriptedField(fieldName: string) { const field = this.fields.getByName(fieldName); if (field) { @@ -424,9 +291,14 @@ export class IndexPattern implements IIndexPattern { field.count = count; try { - const res = await this.savedObjectsClient.update(savedObjectType, this.id, this.prepBody(), { - version: this.version, - }); + const res = await this.savedObjectsClient.update( + 'index-pattern', + this.id, + this.getAsSavedObjectBody(), + { + version: this.version, + } + ); this.version = res.version; } catch (e) { // no need for an error message here @@ -468,24 +340,48 @@ export class IndexPattern implements IIndexPattern { return this.typeMeta?.aggs; } - isWildcard() { + /** + * Does this index pattern title include a '*' + */ + private isWildcard() { return _.includes(this.title, '*'); } - prepBody() { + /** + * Returns index pattern as saved object body for saving + */ + getAsSavedObjectBody() { + const serializeFieldFormatMap = ( + flat: any, + format: FieldFormat | undefined, + field: string | undefined + ) => { + if (format && field) { + flat[field] = format; + } + }; + const serialized = _.transform(this.fieldFormatMap, serializeFieldFormatMap); + const fieldFormatMap = _.isEmpty(serialized) ? undefined : JSON.stringify(serialized); + return { title: this.title, timeFieldName: this.timeFieldName, intervalName: this.intervalName, - sourceFilters: this.mapping.sourceFilters._serialize!(this.sourceFilters), - fields: this.mapping.fields._serialize!(this.fields), - fieldFormatMap: this.mapping.fieldFormatMap._serialize!(this.fieldFormatMap), + sourceFilters: this.sourceFilters ? JSON.stringify(this.sourceFilters) : undefined, + fields: this.fields ? JSON.stringify(this.fields) : undefined, + fieldFormatMap, type: this.type, - typeMeta: this.mapping.typeMeta._serialize!(this.typeMeta), + typeMeta: this.typeMeta ? JSON.stringify(this.typeMeta) : undefined, }; } - getFormatterForField(field: IndexPatternField | IndexPatternField['spec']): FieldFormat { + /** + * Provide a field, get its formatter + * @param field + */ + getFormatterForField( + field: IndexPatternField | IndexPatternField['spec'] | IFieldType + ): FieldFormat { return ( this.fieldFormatMap[field.name] || this.fieldFormats.getDefaultInstance( @@ -494,81 +390,4 @@ export class IndexPattern implements IIndexPattern { ) ); } - - async create(allowOverride: boolean = false) { - const _create = async (duplicateId?: string) => { - if (duplicateId) { - this.patternCache.clear(duplicateId); - await this.savedObjectsClient.delete(savedObjectType, duplicateId); - } - - const body = this.prepBody(); - const response = await this.savedObjectsClient.create(savedObjectType, body, { id: this.id }); - - this.id = response.id; - return response.id; - }; - - const potentialDuplicateByTitle = await findByTitle(this.savedObjectsClient, this.title); - // If there is potentially duplicate title, just create it - if (!potentialDuplicateByTitle) { - return await _create(); - } - - // We found a duplicate but we aren't allowing override, show the warn modal - if (!allowOverride) { - return false; - } - - return await _create(potentialDuplicateByTitle.id); - } - - async _fetchFields() { - const fields = await this.fieldsFetcher.fetch(this); - const scripted = this.getScriptedFields().map((field) => field.spec); - try { - this.fields.replaceAll([...fields, ...scripted]); - } catch (err) { - if (err instanceof FieldTypeUnknownError) { - this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); - } else { - throw err; - } - } - } - - refreshFields() { - return ( - this._fetchFields() - // todo - .then(() => this.indexPatternsService.save(this)) - .catch((err) => { - // https://github.com/elastic/kibana/issues/9224 - // This call will attempt to remap fields from the matching - // ES index which may not actually exist. In that scenario, - // we still want to notify the user that there is a problem - // but we do not want to potentially make any pages unusable - // so do not rethrow the error here - - if (err instanceof IndexPatternMissingIndices) { - this.onNotification({ - title: (err as any).message, - color: 'danger', - iconType: 'alert', - }); - return []; - } - - this.onError(err, { - title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { - defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', - values: { - id: this.id, - title: this.title, - }, - }), - }); - }) - ); - } } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts index d3b3a73a4b50f..b22437ebbdb4e 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts @@ -18,7 +18,7 @@ */ import { defaults } from 'lodash'; -import { IndexPatternsService } from '.'; +import { IndexPatternsService, IndexPattern } from '.'; import { fieldFormatsMock } from '../../field_formats/mocks'; import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_saved_object_index_pattern'; import { UiSettingsCommon, SavedObjectsClientCommon, SavedObject } from '../types'; @@ -31,7 +31,6 @@ const createFieldsFetcher = jest.fn().mockImplementation(() => ({ })); const fieldFormats = fieldFormatsMock; - let object: any = {}; function setDocsourcePayload(id: string | null, providedPayload: any) { @@ -43,16 +42,18 @@ describe('IndexPatterns', () => { let savedObjectsClient: SavedObjectsClientCommon; beforeEach(() => { + const indexPatternObj = { id: 'id', version: 'a', attributes: { title: 'title' } }; savedObjectsClient = {} as SavedObjectsClientCommon; savedObjectsClient.find = jest.fn( - () => - Promise.resolve([{ id: 'id', attributes: { title: 'title' } }]) as Promise< - Array> - > + () => Promise.resolve([indexPatternObj]) as Promise>> ); savedObjectsClient.delete = jest.fn(() => Promise.resolve({}) as Promise); - savedObjectsClient.get = jest.fn().mockImplementation(() => object); savedObjectsClient.create = jest.fn(); + savedObjectsClient.get = jest.fn().mockImplementation(async (type, id) => ({ + id: object.id, + version: object.version, + attributes: object.attributes, + })); savedObjectsClient.update = jest .fn() .mockImplementation(async (type, id, body, { version }) => { @@ -141,30 +142,73 @@ describe('IndexPatterns', () => { }); // Create a normal index patterns - const pattern = await indexPatterns.make('foo'); + const pattern = await indexPatterns.get('foo'); expect(pattern.version).toBe('fooa'); + indexPatterns.clearCache(); // Create the same one - we're going to handle concurrency - const samePattern = await indexPatterns.make('foo'); + const samePattern = await indexPatterns.get('foo'); expect(samePattern.version).toBe('fooaa'); // This will conflict because samePattern did a save (from refreshFields) // but the resave should work fine pattern.title = 'foo2'; - await indexPatterns.save(pattern); + await indexPatterns.updateSavedObject(pattern); // This should not be able to recover samePattern.title = 'foo3'; let result; try { - await indexPatterns.save(samePattern); + await indexPatterns.updateSavedObject(samePattern); } catch (err) { result = err; } expect(result.res.status).toBe(409); }); + + test('create', async () => { + const title = 'kibana-*'; + indexPatterns.refreshFields = jest.fn(); + + const indexPattern = await indexPatterns.create({ title }, true); + expect(indexPattern).toBeInstanceOf(IndexPattern); + expect(indexPattern.title).toBe(title); + expect(indexPatterns.refreshFields).not.toBeCalled(); + + await indexPatterns.create({ title }); + expect(indexPatterns.refreshFields).toBeCalled(); + }); + + test('createAndSave', async () => { + const title = 'kibana-*'; + indexPatterns.createSavedObject = jest.fn(); + indexPatterns.setDefault = jest.fn(); + await indexPatterns.createAndSave({ title }); + expect(indexPatterns.createSavedObject).toBeCalled(); + expect(indexPatterns.setDefault).toBeCalled(); + }); + + test('savedObjectToSpec', () => { + const savedObject = { + id: 'id', + version: 'version', + attributes: { + title: 'kibana-*', + timeFieldName: '@timestamp', + fields: '[]', + sourceFilters: '[{"value":"item1"},{"value":"item2"}]', + fieldFormatMap: '{"field":{}}', + typeMeta: '{}', + type: '', + }, + type: 'index-pattern', + references: [], + }; + + expect(indexPatterns.savedObjectToSpec(savedObject)).toMatchSnapshot(); + }); }); diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 47484f8ec75bb..eef8ef10ea754 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -33,9 +33,17 @@ import { IIndexPatternsApiClient, GetFieldsOptions, IndexPatternSpec, + IndexPatternAttributes, + FieldSpec, + FieldFormatMap, + IndexPatternFieldMap, } from '../types'; import { FieldFormatsStartCommon } from '../../field_formats'; import { UI_SETTINGS, SavedObject } from '../../../common'; +import { SavedObjectNotFound } from '../../../../kibana_utils/common'; +import { IndexPatternMissingIndices } from '../lib'; +import { findByTitle } from '../utils'; +import { DuplicateIndexPatternError } from '../errors'; const indexPatternCache = createIndexPatternCache(); const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; @@ -86,6 +94,9 @@ export class IndexPatternsService { ); } + /** + * Refresh cache of index pattern ids and titles + */ private async refreshSavedObjectsCache() { this.savedObjectsCache = await this.savedObjectsClient.find({ type: 'index-pattern', @@ -94,6 +105,10 @@ export class IndexPatternsService { }); } + /** + * Get list of index pattern ids + * @param refresh Force refresh of index pattern list + */ getIds = async (refresh: boolean = false) => { if (!this.savedObjectsCache || refresh) { await this.refreshSavedObjectsCache(); @@ -104,6 +119,10 @@ export class IndexPatternsService { return this.savedObjectsCache.map((obj) => obj?.id); }; + /** + * Get list of index pattern titles + * @param refresh Force refresh of index pattern list + */ getTitles = async (refresh: boolean = false): Promise => { if (!this.savedObjectsCache || refresh) { await this.refreshSavedObjectsCache(); @@ -114,14 +133,29 @@ export class IndexPatternsService { return this.savedObjectsCache.map((obj) => obj?.attributes?.title); }; - getFieldsForTimePattern = (options: GetFieldsOptions = {}) => { - return this.apiClient.getFieldsForTimePattern(options); - }; - - getFieldsForWildcard = (options: GetFieldsOptions = {}) => { - return this.apiClient.getFieldsForWildcard(options); + /** + * Get list of index pattern ids with titles + * @param refresh Force refresh of index pattern list + */ + getIdsWithTitle = async ( + refresh: boolean = false + ): Promise> => { + if (!this.savedObjectsCache || refresh) { + await this.refreshSavedObjectsCache(); + } + if (!this.savedObjectsCache) { + return []; + } + return this.savedObjectsCache.map((obj) => ({ + id: obj?.id, + title: obj?.attributes?.title, + })); }; + /** + * Clear index pattern list cache + * @param id optionally clear a single id + */ clearCache = (id?: string) => { this.savedObjectsCache = null; if (id) { @@ -130,6 +164,7 @@ export class IndexPatternsService { indexPatternCache.clearAll(); } }; + getCache = async () => { if (!this.savedObjectsCache) { await this.refreshSavedObjectsCache(); @@ -137,6 +172,9 @@ export class IndexPatternsService { return this.savedObjectsCache; }; + /** + * Get default index pattern + */ getDefault = async () => { const defaultIndexPatternId = await this.config.get('defaultIndex'); if (defaultIndexPatternId) { @@ -146,47 +184,350 @@ export class IndexPatternsService { return null; }; + /** + * Optionally set default index pattern, unless force = true + * @param id + * @param force + */ + setDefault = async (id: string, force = false) => { + if (force || !this.config.get('defaultIndex')) { + await this.config.set('defaultIndex', id); + } + }; + + private isFieldRefreshRequired(specs?: IndexPatternFieldMap): boolean { + if (!specs) { + return true; + } + + return Object.values(specs).every((spec) => { + // See https://github.com/elastic/kibana/pull/8421 + const hasFieldCaps = 'aggregatable' in spec && 'searchable' in spec; + + // See https://github.com/elastic/kibana/pull/11969 + const hasDocValuesFlag = 'readFromDocValues' in spec; + + return !hasFieldCaps || !hasDocValuesFlag; + }); + } + + /** + * Get field list by providing { pattern } + * @param options + */ + getFieldsForWildcard = async (options: GetFieldsOptions = {}) => { + const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); + return this.apiClient.getFieldsForWildcard({ + pattern: options.pattern, + metaFields, + type: options.type, + params: options.params || {}, + }); + }; + + /** + * Get field list by providing an index patttern (or spec) + * @param options + */ + getFieldsForIndexPattern = async ( + indexPattern: IndexPattern | IndexPatternSpec, + options: GetFieldsOptions = {} + ) => + this.getFieldsForWildcard({ + pattern: indexPattern.title as string, + ...options, + type: indexPattern.type, + params: indexPattern.typeMeta && indexPattern.typeMeta.params, + }); + + /** + * Refresh field list for a given index pattern + * @param indexPattern + */ + refreshFields = async (indexPattern: IndexPattern) => { + try { + const fields = await this.getFieldsForIndexPattern(indexPattern); + const scripted = indexPattern.getScriptedFields().map((field) => field.spec); + indexPattern.fields.replaceAll([...fields, ...scripted]); + } catch (err) { + if (err instanceof IndexPatternMissingIndices) { + this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); + } + + this.onError(err, { + title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', + values: { id: indexPattern.id, title: indexPattern.title }, + }), + }); + } + }; + + /** + * Refreshes a field list from a spec before an index pattern instance is created + * @param fields + * @param id + * @param title + * @param options + */ + private refreshFieldSpecMap = async ( + fields: IndexPatternFieldMap, + id: string, + title: string, + options: GetFieldsOptions + ) => { + const scriptdFields = Object.values(fields).filter((field) => field.scripted); + try { + const newFields = await this.getFieldsForWildcard(options); + return this.fieldArrayToMap([...newFields, ...scriptdFields]); + } catch (err) { + if (err instanceof IndexPatternMissingIndices) { + this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); + return {}; + } + + this.onError(err, { + title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', + values: { id, title }, + }), + }); + } + return fields; + }; + + /** + * Applies a set of formats to a set of fields + * @param fieldSpecs + * @param fieldFormatMap + */ + private addFormatsToFields = (fieldSpecs: FieldSpec[], fieldFormatMap: FieldFormatMap) => { + Object.entries(fieldFormatMap).forEach(([fieldName, value]) => { + const field = fieldSpecs.find((fld: FieldSpec) => fld.name === fieldName); + if (field) { + field.format = value; + } + }); + }; + + /** + * Converts field array to map + * @param fields + */ + fieldArrayToMap = (fields: FieldSpec[]) => + fields.reduce((collector, field) => { + collector[field.name] = field; + return collector; + }, {}); + + /** + * Converts index pattern saved object to index pattern spec + * @param savedObject + */ + + savedObjectToSpec = (savedObject: SavedObject): IndexPatternSpec => { + const { + id, + version, + attributes: { + title, + timeFieldName, + intervalName, + fields, + sourceFilters, + fieldFormatMap, + typeMeta, + type, + }, + } = savedObject; + + const parsedSourceFilters = sourceFilters ? JSON.parse(sourceFilters) : undefined; + const parsedTypeMeta = typeMeta ? JSON.parse(typeMeta) : undefined; + const parsedFieldFormatMap = fieldFormatMap ? JSON.parse(fieldFormatMap) : {}; + const parsedFields: FieldSpec[] = fields ? JSON.parse(fields) : []; + + this.addFormatsToFields(parsedFields, parsedFieldFormatMap); + return { + id, + version, + title, + intervalName, + timeFieldName, + sourceFilters: parsedSourceFilters, + fields: this.fieldArrayToMap(parsedFields), + typeMeta: parsedTypeMeta, + type, + }; + }; + + /** + * Get an index pattern by id. Cache optimized + * @param id + */ + get = async (id: string): Promise => { const cache = indexPatternCache.get(id); if (cache) { return cache; } - const indexPattern = await this.make(id); + const savedObject = await this.savedObjectsClient.get( + savedObjectType, + id + ); + + if (!savedObject.version) { + throw new SavedObjectNotFound(savedObjectType, id, 'management/kibana/indexPatterns'); + } + + const spec = this.savedObjectToSpec(savedObject); + const { title, type, typeMeta } = spec; + const parsedFieldFormats: FieldFormatMap = savedObject.attributes.fieldFormatMap + ? JSON.parse(savedObject.attributes.fieldFormatMap) + : {}; + + const isFieldRefreshRequired = this.isFieldRefreshRequired(spec.fields); + let isSaveRequired = isFieldRefreshRequired; + try { + spec.fields = isFieldRefreshRequired + ? await this.refreshFieldSpecMap(spec.fields || {}, id, spec.title as string, { + pattern: title, + metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), + type, + params: typeMeta && typeMeta.params, + }) + : spec.fields; + } catch (err) { + isSaveRequired = false; + if (err instanceof IndexPatternMissingIndices) { + this.onNotification({ + title: (err as any).message, + color: 'danger', + iconType: 'alert', + }); + } else { + this.onError(err, { + title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', + values: { id, title }, + }), + }); + } + } + + Object.entries(parsedFieldFormats).forEach(([fieldName, value]) => { + const field = spec.fields?.[fieldName]; + if (field) { + field.format = value; + } + }); + + const indexPattern = await this.create(spec, true); + indexPatternCache.set(id, indexPattern); + if (isSaveRequired) { + try { + this.updateSavedObject(indexPattern); + } catch (err) { + this.onError(err, { + title: i18n.translate('data.indexPatterns.fetchFieldSaveErrorTitle', { + defaultMessage: + 'Error saving after fetching fields for index pattern {title} (ID: {id})', + values: { + id: indexPattern.id, + title: indexPattern.title, + }, + }), + }); + } + } - return indexPatternCache.set(id, indexPattern); + indexPattern.resetOriginalSavedObjectBody(); + return indexPattern; }; - async specToIndexPattern(spec: IndexPatternSpec) { + /** + * Create a new index pattern instance + * @param spec + * @param skipFetchFields + */ + async create(spec: IndexPatternSpec, skipFetchFields = false): Promise { const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); - const indexPattern = new IndexPattern(spec.id, { + const indexPattern = new IndexPattern({ + spec, savedObjectsClient: this.savedObjectsClient, - apiClient: this.apiClient, - patternCache: indexPatternCache, fieldFormats: this.fieldFormats, - indexPatternsService: this, - onNotification: this.onNotification, - onError: this.onError, shortDotsEnable, metaFields, }); - indexPattern.initFromSpec(spec); + if (!skipFetchFields) { + await this.refreshFields(indexPattern); + } + + return indexPattern; + } + + /** + * Create a new index pattern and save it right away + * @param spec + * @param override Overwrite if existing index pattern exists + * @param skipFetchFields + */ + + async createAndSave(spec: IndexPatternSpec, override = false, skipFetchFields = false) { + const indexPattern = await this.create(spec, skipFetchFields); + await this.createSavedObject(indexPattern, override); + await this.setDefault(indexPattern.id as string); + return indexPattern; + } + + /** + * Save a new index pattern + * @param indexPattern + * @param override Overwrite if existing index pattern exists + */ + + async createSavedObject(indexPattern: IndexPattern, override = false) { + const dupe = await findByTitle(this.savedObjectsClient, indexPattern.title); + if (dupe) { + if (override) { + await this.delete(dupe.id); + } else { + throw new DuplicateIndexPatternError(`Duplicate index pattern: ${indexPattern.title}`); + } + } + + const body = indexPattern.getAsSavedObjectBody(); + const response = await this.savedObjectsClient.create(savedObjectType, body, { + id: indexPattern.id, + }); + indexPattern.id = response.id; + indexPatternCache.set(indexPattern.id, indexPattern); return indexPattern; } - async save(indexPattern: IndexPattern, saveAttempts: number = 0): Promise { + /** + * Save existing index pattern. Will attempt to merge differences if there are conflicts + * @param indexPattern + * @param saveAttempts + */ + + async updateSavedObject( + indexPattern: IndexPattern, + saveAttempts: number = 0 + ): Promise { if (!indexPattern.id) return; - const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); - const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); - const body = indexPattern.prepBody(); + // get the list of attributes + const body = indexPattern.getAsSavedObjectBody(); + const originalBody = indexPattern.getOriginalSavedObjectBody(); + // get changed keys const originalChangedKeys: string[] = []; Object.entries(body).forEach(([key, value]) => { - if (value !== indexPattern.originalBody[key]) { + if (value !== (originalBody as any)[key]) { originalChangedKeys.push(key); } }); @@ -197,92 +538,60 @@ export class IndexPatternsService { indexPattern.id = resp.id; indexPattern.version = resp.version; }) - .catch((err) => { + .catch(async (err) => { if (err?.res?.status === 409 && saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS) { - const samePattern = new IndexPattern(indexPattern.id, { - savedObjectsClient: this.savedObjectsClient, - apiClient: this.apiClient, - patternCache: indexPatternCache, - fieldFormats: this.fieldFormats, - indexPatternsService: this, - onNotification: this.onNotification, - onError: this.onError, - shortDotsEnable, - metaFields, + const samePattern = await this.get(indexPattern.id as string); + // What keys changed from now and what the server returned + const updatedBody = samePattern.getAsSavedObjectBody(); + + // Build a list of changed keys from the server response + // and ensure we ignore the key if the server response + // is the same as the original response (since that is expected + // if we made a change in that key) + + const serverChangedKeys: string[] = []; + Object.entries(updatedBody).forEach(([key, value]) => { + if (value !== (body as any)[key] && value !== (originalBody as any)[key]) { + serverChangedKeys.push(key); + } }); - return samePattern.init().then(() => { - // What keys changed from now and what the server returned - const updatedBody = samePattern.prepBody(); - - // Build a list of changed keys from the server response - // and ensure we ignore the key if the server response - // is the same as the original response (since that is expected - // if we made a change in that key) - - const serverChangedKeys: string[] = []; - Object.entries(updatedBody).forEach(([key, value]) => { - if (value !== (body as any)[key] && value !== indexPattern.originalBody[key]) { - serverChangedKeys.push(key); - } - }); - - let unresolvedCollision = false; - for (const originalKey of originalChangedKeys) { - for (const serverKey of serverChangedKeys) { - if (originalKey === serverKey) { - unresolvedCollision = true; - break; - } + let unresolvedCollision = false; + for (const originalKey of originalChangedKeys) { + for (const serverKey of serverChangedKeys) { + if (originalKey === serverKey) { + unresolvedCollision = true; + break; } } + } - if (unresolvedCollision) { - const title = i18n.translate('data.indexPatterns.unableWriteLabel', { - defaultMessage: - 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', - }); - - this.onNotification({ title, color: 'danger' }); - throw err; - } - - // Set the updated response on this object - serverChangedKeys.forEach((key) => { - (indexPattern as any)[key] = (samePattern as any)[key]; + if (unresolvedCollision) { + const title = i18n.translate('data.indexPatterns.unableWriteLabel', { + defaultMessage: + 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', }); - indexPattern.version = samePattern.version; - // Clear cache - indexPatternCache.clear(indexPattern.id!); + this.onNotification({ title, color: 'danger' }); + throw err; + } - // Try the save again - return this.save(indexPattern, saveAttempts); + // Set the updated response on this object + serverChangedKeys.forEach((key) => { + (indexPattern as any)[key] = (samePattern as any)[key]; }); + indexPattern.version = samePattern.version; + + // Clear cache + indexPatternCache.clear(indexPattern.id!); + + // Try the save again + return this.updateSavedObject(indexPattern, saveAttempts); } throw err; }); } - async make(id?: string): Promise { - const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); - const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); - - const indexPattern = new IndexPattern(id, { - savedObjectsClient: this.savedObjectsClient, - apiClient: this.apiClient, - patternCache: indexPatternCache, - fieldFormats: this.fieldFormats, - indexPatternsService: this, - onNotification: this.onNotification, - onError: this.onError, - shortDotsEnable, - metaFields, - }); - - return indexPattern.init(); - } - /** * Deletes an index pattern from .kibana index * @param indexPatternId: Id of kibana Index Pattern to delete diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 7a230c20f6cd0..cb0c3aa0de38e 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -22,29 +22,23 @@ import { ToastInputFields, ErrorToastOptions } from 'src/core/public/notificatio import type { SavedObject } from 'src/core/server'; import { IFieldType } from './fields'; import { SerializedFieldFormat } from '../../../expressions/common'; -import { KBN_FIELD_TYPES } from '..'; +import { KBN_FIELD_TYPES, IndexPatternField, FieldFormat } from '..'; + +export type FieldFormatMap = Record; export interface IIndexPattern { - [key: string]: any; fields: IFieldType[]; title: string; id?: string; type?: string; timeFieldName?: string; getTimeField?(): IFieldType | undefined; - fieldFormatMap?: Record< - string, - { - id: string; - params: unknown; - } - >; + fieldFormatMap?: Record | undefined>; + getFormatterForField?: ( + field: IndexPatternField | IndexPatternField['spec'] | IFieldType + ) => FieldFormat; } -/** - * Use data plugin interface instead - * @deprecated - */ export interface IndexPatternAttributes { type: string; fields: string; @@ -166,15 +160,18 @@ export interface FieldSpec { indexed?: boolean; } +export type IndexPatternFieldMap = Record; + export interface IndexPatternSpec { id?: string; version?: string; - - title: string; + title?: string; + intervalName?: string; timeFieldName?: string; sourceFilters?: SourceFilter[]; - fields?: FieldSpec[]; + fields?: IndexPatternFieldMap; typeMeta?: TypeMeta; + type?: string; } export interface SourceFilter { diff --git a/src/plugins/data/common/search/aggs/buckets/histogram.ts b/src/plugins/data/common/search/aggs/buckets/histogram.ts index 4b631e1fd7cd7..c3d3f041dd0c7 100644 --- a/src/plugins/data/common/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/common/search/aggs/buckets/histogram.ts @@ -158,6 +158,7 @@ export const getHistogramBucketAgg = ({ maxBucketsUiSettings: getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS), maxBucketsUserInput: aggConfig.params.maxBars, intervalBase: aggConfig.params.intervalBase, + esTypes: aggConfig.params.field?.spec?.esTypes || [], }); }, }, diff --git a/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts index d3a95b32cd425..7e5e20e5917aa 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts @@ -21,6 +21,7 @@ import { calculateHistogramInterval, CalculateHistogramIntervalParams, } from './histogram_calculate_interval'; +import { ES_FIELD_TYPES } from '../../../../types'; describe('calculateHistogramInterval', () => { describe('auto calculating mode', () => { @@ -36,10 +37,91 @@ describe('calculateHistogramInterval', () => { min: 0, max: 1, }, + esTypes: [], }; }); describe('maxBucketsUserInput is defined', () => { + test('should set 1 as an interval for integer numbers that are less than maxBuckets #1', () => { + const p = { + ...params, + maxBucketsUserInput: 100, + values: { + min: 1, + max: 50, + }, + esTypes: [ES_FIELD_TYPES.INTEGER], + }; + expect(calculateHistogramInterval(p)).toEqual(1); + }); + + test('should set 1 as an interval for integer numbers that are less than maxBuckets #2', () => { + const p = { + ...params, + maxBucketsUiSettings: 1000, + maxBucketsUserInput: 258, + values: { + min: 521, + max: 689, + }, + esTypes: [ES_FIELD_TYPES.INTEGER], + }; + expect(calculateHistogramInterval(p)).toEqual(1); + }); + + test('should set correct interval for integer numbers that are greater than maxBuckets #1', () => { + const p = { + ...params, + maxBucketsUserInput: 100, + values: { + min: 400, + max: 790, + }, + esTypes: [ES_FIELD_TYPES.INTEGER, ES_FIELD_TYPES.SHORT], + }; + expect(calculateHistogramInterval(p)).toEqual(5); + }); + + test('should set correct interval for integer numbers that are greater than maxBuckets #2', () => { + // diff === 3456211; interval === 50000; buckets === 69 + const p = { + ...params, + maxBucketsUserInput: 100, + values: { + min: 567, + max: 3456778, + }, + esTypes: [ES_FIELD_TYPES.LONG], + }; + expect(calculateHistogramInterval(p)).toEqual(50000); + }); + + test('should not set integer interval if the field type is float #1', () => { + const p = { + ...params, + maxBucketsUserInput: 100, + values: { + min: 0, + max: 1, + }, + esTypes: [ES_FIELD_TYPES.FLOAT], + }; + expect(calculateHistogramInterval(p)).toEqual(0.01); + }); + + test('should not set integer interval if the field type is float #2', () => { + const p = { + ...params, + maxBucketsUserInput: 100, + values: { + min: 0, + max: 1, + }, + esTypes: [ES_FIELD_TYPES.INTEGER, ES_FIELD_TYPES.FLOAT], + }; + expect(calculateHistogramInterval(p)).toEqual(0.01); + }); + test('should not set interval which more than largest possible', () => { const p = { ...params, @@ -48,6 +130,7 @@ describe('calculateHistogramInterval', () => { min: 150, max: 250, }, + esTypes: [ES_FIELD_TYPES.SHORT], }; expect(calculateHistogramInterval(p)).toEqual(1); }); @@ -61,6 +144,7 @@ describe('calculateHistogramInterval', () => { min: 0.1, max: 0.9, }, + esTypes: [ES_FIELD_TYPES.FLOAT], }) ).toBe(0.02); }); @@ -74,6 +158,7 @@ describe('calculateHistogramInterval', () => { min: 10.45, max: 1000.05, }, + esTypes: [ES_FIELD_TYPES.FLOAT], }) ).toBe(100); }); @@ -88,6 +173,7 @@ describe('calculateHistogramInterval', () => { min: 0, max: 100, }, + esTypes: [ES_FIELD_TYPES.BYTE], }) ).toEqual(1); }); @@ -100,8 +186,9 @@ describe('calculateHistogramInterval', () => { min: 1, max: 10, }, + esTypes: [ES_FIELD_TYPES.INTEGER], }) - ).toEqual(0.1); + ).toEqual(1); }); test('should set intervals for integer numbers (diff more than maxBucketsUiSettings)', () => { @@ -113,6 +200,7 @@ describe('calculateHistogramInterval', () => { min: 45678, max: 90123, }, + esTypes: [ES_FIELD_TYPES.INTEGER], }) ).toEqual(500); }); @@ -127,6 +215,7 @@ describe('calculateHistogramInterval', () => { min: 1.245, max: 2.9, }, + esTypes: [ES_FIELD_TYPES.FLOAT], }) ).toEqual(0.02); expect( @@ -136,6 +225,7 @@ describe('calculateHistogramInterval', () => { min: 0.5, max: 2.3, }, + esTypes: [ES_FIELD_TYPES.FLOAT], }) ).toEqual(0.02); }); @@ -149,6 +239,7 @@ describe('calculateHistogramInterval', () => { min: 0.1, max: 0.9, }, + esTypes: [ES_FIELD_TYPES.FLOAT], }) ).toBe(0.01); }); diff --git a/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts index 378340e876296..313ecf1000f41 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts @@ -18,6 +18,7 @@ */ import { isAutoInterval } from '../_interval_options'; +import { ES_FIELD_TYPES } from '../../../../types'; interface IntervalValuesRange { min: number; @@ -28,6 +29,7 @@ export interface CalculateHistogramIntervalParams { interval: string; maxBucketsUiSettings: number; maxBucketsUserInput?: number | ''; + esTypes: ES_FIELD_TYPES[]; intervalBase?: number; values?: IntervalValuesRange; } @@ -77,11 +79,27 @@ const calculateForGivenInterval = ( - The lower power of 10, times 2 - The lower power of 10, times 5 **/ -const calculateAutoInterval = (diff: number, maxBars: number) => { +const calculateAutoInterval = (diff: number, maxBars: number, esTypes: ES_FIELD_TYPES[]) => { const exactInterval = diff / maxBars; - const lowerPower = Math.pow(10, Math.floor(Math.log10(exactInterval))); + // For integer fields that are less than maxBars, we should use 1 as the value of interval + // Elastic has 4 integer data types: long, integer, short, byte + // see: https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html + if ( + diff < maxBars && + esTypes.every((esType) => + [ + ES_FIELD_TYPES.INTEGER, + ES_FIELD_TYPES.LONG, + ES_FIELD_TYPES.SHORT, + ES_FIELD_TYPES.BYTE, + ].includes(esType) + ) + ) { + return 1; + } + const lowerPower = Math.pow(10, Math.floor(Math.log10(exactInterval))); const autoBuckets = diff / lowerPower; if (autoBuckets > maxBars) { @@ -103,6 +121,7 @@ export const calculateHistogramInterval = ({ maxBucketsUserInput, intervalBase, values, + esTypes, }: CalculateHistogramIntervalParams) => { const isAuto = isAutoInterval(interval); let calculatedInterval = isAuto ? 0 : parseFloat(interval); @@ -119,8 +138,10 @@ export const calculateHistogramInterval = ({ calculatedInterval = isAuto ? calculateAutoInterval( diff, + // Mind maxBucketsUserInput can be an empty string, hence we need to ensure it here - Math.min(maxBucketsUiSettings, maxBucketsUserInput || maxBucketsUiSettings) + Math.min(maxBucketsUiSettings, maxBucketsUserInput || maxBucketsUiSettings), + esTypes ) : calculateForGivenInterval(diff, calculatedInterval, maxBucketsUiSettings); } diff --git a/src/plugins/data/common/search/es_search/index.ts b/src/plugins/data/common/search/es_search/index.ts index d8f7b5091eb8f..8e8897c7d7517 100644 --- a/src/plugins/data/common/search/es_search/index.ts +++ b/src/plugins/data/common/search/es_search/index.ts @@ -18,3 +18,4 @@ */ export * from './types'; +export * from './utils'; diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts index 81124c1e095f7..b1c3e5cdd3960 100644 --- a/src/plugins/data/common/search/es_search/types.ts +++ b/src/plugins/data/common/search/es_search/types.ts @@ -37,22 +37,8 @@ export type ISearchRequestParams> = { trackTotalHits?: boolean; } & Search; -export interface IEsSearchRequest extends IKibanaSearchRequest { - params?: ISearchRequestParams; +export interface IEsSearchRequest extends IKibanaSearchRequest { indexType?: string; } -export interface IEsSearchResponse extends IKibanaSearchResponse { - /** - * Indicates whether async search is still in flight - */ - isRunning?: boolean; - /** - * Indicates whether the results returned are complete or partial - */ - isPartial?: boolean; - rawResponse: SearchResponse; -} - -export const isEsResponse = (response: any): response is IEsSearchResponse => - response && response.rawResponse; +export type IEsSearchResponse = IKibanaSearchResponse>; diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_values_at_type.js b/src/plugins/data/common/search/es_search/utils.ts similarity index 54% rename from src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_values_at_type.js rename to src/plugins/data/common/search/es_search/utils.ts index 229c5be24aac5..ec66a3d3f923e 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_values_at_type.js +++ b/src/plugins/data/common/search/es_search/utils.ts @@ -17,14 +17,25 @@ * under the License. */ -import { createTypeReducer, flatConcat, mergeWith } from './lib'; +import { IKibanaSearchResponse } from '..'; /** - * Reducer that merges specs by concatenating the values of - * all keys in accumulator and spec with the same logic as concat - * @param {[type]} initial [description] - * @return {[type]} [description] + * @returns true if response had an error while executing in ES */ -export const flatConcatValuesAtType = createTypeReducer((objectA, objectB) => - mergeWith(objectA || {}, objectB || {}, flatConcat) -); +export const isErrorResponse = (response?: IKibanaSearchResponse) => { + return !response || (!response.isRunning && response.isPartial); +}; + +/** + * @returns true if response is completed successfully + */ +export const isCompleteResponse = (response?: IKibanaSearchResponse) => { + return response && !response.isRunning && !response.isPartial; +}; + +/** + * @returns true if request is still running an/d response contains partial results + */ +export const isPartialResponse = (response?: IKibanaSearchResponse) => { + return response && response.isRunning && response.isPartial; +}; diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index 0a299b57275f8..c3943af5c6ff7 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -26,14 +26,14 @@ export type ISearch = ( ) => Observable; export type ISearchGeneric = < - SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse + SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( request: SearchStrategyRequest, options?: ISearchOptions ) => Observable; -export interface IKibanaSearchResponse { +export interface IKibanaSearchResponse { /** * Some responses may contain a unique id to identify the request this response came from. */ @@ -50,16 +50,25 @@ export interface IKibanaSearchResponse { * that represents how progress is indicated. */ loaded?: number; + + /** + * Indicates whether search is still in flight + */ + isRunning?: boolean; + + /** + * Indicates whether the results returned are complete or partial + */ + isPartial?: boolean; + + rawResponse: RawResponse; } -export interface IKibanaSearchRequest { +export interface IKibanaSearchRequest { /** * An id can be used to uniquely identify this request. */ id?: string; - /** - * Optionally tell search strategies to output debug information. - */ - debug?: boolean; + params?: Params; } diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 5038af9409316..f7dceffa9fdbc 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -230,6 +230,8 @@ import { formatHitProvider, } from './index_patterns'; +export type { IndexPatternsService } from './index_patterns'; + // Index patterns namespace: export const indexPatterns = { ILLEGAL_CHARACTERS_KEY, @@ -262,9 +264,12 @@ export { UI_SETTINGS, TypeMeta as IndexPatternTypeMeta, AggregationRestrictions as IndexPatternAggRestrictions, + IndexPatternSpec, fieldList, } from '../common'; +export { DuplicateIndexPatternError } from '../common/index_patterns/errors'; + /* * Autocomplete query suggestions: */ @@ -374,7 +379,7 @@ export { export type { SearchSource } from './search'; -export { ISearchOptions } from '../common'; +export { ISearchOptions, isErrorResponse, isCompleteResponse, isPartialResponse } from '../common'; // Search namespace export const search = { @@ -415,6 +420,7 @@ export { StatefulSearchBarProps, FilterBar, QueryStringInput, + QueryStringInputProps, IndexPatternSelect, } from './ui'; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index db8d9dba4e0c7..6b419f6995447 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -454,6 +454,13 @@ export interface DataPublicPluginStartUi { SearchBar: React.ComponentType; } +// Warning: (ae-missing-release-tag) "DuplicateIndexPatternError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class DuplicateIndexPatternError extends Error { + constructor(message: string); +} + // @public (undocumented) export enum ES_FIELD_TYPES { // (undocumented) @@ -911,22 +918,15 @@ export interface IDataPluginServices extends Partial { // Warning: (ae-missing-release-tag) "IEsSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IEsSearchRequest extends IKibanaSearchRequest { +export interface IEsSearchRequest extends IKibanaSearchRequest { // (undocumented) indexType?: string; - // (undocumented) - params?: ISearchRequestParams; } // Warning: (ae-missing-release-tag) "IEsSearchResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IEsSearchResponse extends IKibanaSearchResponse { - isPartial?: boolean; - isRunning?: boolean; - // (undocumented) - rawResponse: SearchResponse; -} +export type IEsSearchResponse = IKibanaSearchResponse>; // Warning: (ae-missing-release-tag) "IFieldFormat" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1004,16 +1004,15 @@ export interface IFieldType { // // @public (undocumented) export interface IIndexPattern { + // Warning: (ae-forgotten-export) The symbol "SerializedFieldFormat" needs to be exported by the entry point index.d.ts + // // (undocumented) - [key: string]: any; - // (undocumented) - fieldFormatMap?: Record; + fieldFormatMap?: Record | undefined>; // (undocumented) fields: IFieldType[]; // (undocumented) + getFormatterForField?: (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat; + // (undocumented) getTimeField?(): IFieldType | undefined; // (undocumented) id?: string; @@ -1043,10 +1042,12 @@ export interface IIndexPatternFieldList extends Array { removeAll(): void; // (undocumented) replaceAll(specs: FieldSpec[]): void; + // Warning: (ae-forgotten-export) The symbol "IndexPatternFieldMap" needs to be exported by the entry point index.d.ts + // // (undocumented) toSpec(options?: { getFormatterForField?: IndexPattern['getFormatterForField']; - }): FieldSpec[]; + }): IndexPatternFieldMap; // (undocumented) update(field: FieldSpec): void; } @@ -1054,17 +1055,22 @@ export interface IIndexPatternFieldList extends Array { // Warning: (ae-missing-release-tag) "IKibanaSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IKibanaSearchRequest { - debug?: boolean; +export interface IKibanaSearchRequest { id?: string; + // (undocumented) + params?: Params; } // Warning: (ae-missing-release-tag) "IKibanaSearchResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IKibanaSearchResponse { +export interface IKibanaSearchResponse { id?: string; + isPartial?: boolean; + isRunning?: boolean; loaded?: number; + // (undocumented) + rawResponse: RawResponse; total?: number; } @@ -1079,27 +1085,23 @@ export type IMetricAggType = MetricAggType; // @public (undocumented) export class IndexPattern implements IIndexPattern { // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts - constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); - // (undocumented) - addScriptedField(name: string, script: string, fieldType: string | undefined, lang: string): Promise; - // (undocumented) - create(allowOverride?: boolean): Promise; - // (undocumented) - _fetchFields(): Promise; + constructor({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); + addScriptedField(name: string, script: string, fieldType?: string, lang?: string): Promise; // (undocumented) - fieldFormatMap: any; + fieldFormatMap: Record; // (undocumented) fields: IIndexPatternFieldList & { - toSpec: () => FieldSpec[]; + toSpec: () => IndexPatternFieldMap; }; // (undocumented) - fieldsFetcher: any; + flattenHit: (hit: Record, deep?: boolean) => Record; // (undocumented) - flattenHit: any; + formatField: FormatFieldFn; // (undocumented) - formatField: any; - // (undocumented) - formatHit: any; + formatHit: { + (hit: Record, type?: string): any; + formatField: FormatFieldFn; + }; // (undocumented) getAggregationRestrictions(): Record> | undefined; + getAsSavedObjectBody(): { + title: string; + timeFieldName: string | undefined; + intervalName: string | undefined; + sourceFilters: string | undefined; + fields: string | undefined; + fieldFormatMap: string | undefined; + type: string | undefined; + typeMeta: string | undefined; + }; // (undocumented) getComputedFields(): { storedFields: string[]; @@ -1120,13 +1132,21 @@ export class IndexPattern implements IIndexPattern { }; // (undocumented) getFieldByName(name: string): IndexPatternField | undefined; - // (undocumented) - getFormatterForField(field: IndexPatternField | IndexPatternField['spec']): FieldFormat; + getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; // (undocumented) getNonScriptedFields(): IndexPatternField[]; + getOriginalSavedObjectBody: () => { + title?: string | undefined; + timeFieldName?: string | undefined; + intervalName?: string | undefined; + fields?: string | undefined; + sourceFilters?: string | undefined; + fieldFormatMap?: string | undefined; + typeMeta?: string | undefined; + type?: string | undefined; + }; // (undocumented) getScriptedFields(): IndexPatternField[]; - // (undocumented) getSourceFiltering(): { excludes: any[]; }; @@ -1135,12 +1155,6 @@ export class IndexPattern implements IIndexPattern { // (undocumented) id?: string; // (undocumented) - init(): Promise; - // Warning: (ae-forgotten-export) The symbol "IndexPatternSpec" needs to be exported by the entry point index.d.ts - // - // (undocumented) - initFromSpec(spec: IndexPatternSpec): this; - // (undocumented) intervalName: string | undefined; // (undocumented) isTimeBased(): boolean; @@ -1149,30 +1163,11 @@ export class IndexPattern implements IIndexPattern { // (undocumented) isTimeNanosBased(): boolean; // (undocumented) - isWildcard(): boolean; - // (undocumented) metaFields: string[]; // (undocumented) - originalBody: { - [key: string]: any; - }; - // (undocumented) popularizeField(fieldName: string, unit?: number): Promise; - // (undocumented) - prepBody(): { - title: string; - timeFieldName: string | undefined; - intervalName: string | undefined; - sourceFilters: string | undefined; - fields: string | undefined; - fieldFormatMap: string | undefined; - type: string | undefined; - typeMeta: string | undefined; - }; - // (undocumented) - refreshFields(): Promise; - // (undocumented) removeScriptedField(fieldName: string): void; + resetOriginalSavedObjectBody: () => void; // Warning: (ae-forgotten-export) The symbol "SourceFilter" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1205,7 +1200,7 @@ export type IndexPatternAggRestrictions = Record | undefined; set conflictDescriptions(conflictDescriptions: Record | undefined); - // (undocumented) get count(): number; set count(count: number); // (undocumented) @@ -1244,14 +1237,12 @@ export class IndexPatternField implements IFieldType { get esTypes(): string[] | undefined; // (undocumented) get filterable(): boolean; - // (undocumented) get lang(): string | undefined; set lang(lang: string | undefined); // (undocumented) get name(): string; // (undocumented) get readFromDocValues(): boolean; - // (undocumented) get script(): string | undefined; set script(script: string | undefined); // (undocumented) @@ -1282,24 +1273,7 @@ export class IndexPatternField implements IFieldType { // (undocumented) toSpec({ getFormatterForField, }?: { getFormatterForField?: IndexPattern['getFormatterForField']; - }): { - count: number; - script: string | undefined; - lang: string | undefined; - conflictDescriptions: Record | undefined; - name: string; - type: string; - esTypes: string[] | undefined; - scripted: boolean; - searchable: boolean; - aggregatable: boolean; - readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; - format: { - id: any; - params: any; - } | undefined; - }; + }): FieldSpec; // (undocumented) get type(): string; // (undocumented) @@ -1323,7 +1297,6 @@ export const indexPatterns: { formatHitProvider: typeof formatHitProvider; }; -// Warning: (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "IndexPatternsContract" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1356,6 +1329,67 @@ export class IndexPatternSelect extends Component { UNSAFE_componentWillReceiveProps(nextProps: IndexPatternSelectProps): void; } +// Warning: (ae-missing-release-tag) "IndexPatternSpec" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IndexPatternSpec { + // (undocumented) + fields?: IndexPatternFieldMap; + // (undocumented) + id?: string; + // (undocumented) + intervalName?: string; + // (undocumented) + sourceFilters?: SourceFilter[]; + // (undocumented) + timeFieldName?: string; + // (undocumented) + title?: string; + // (undocumented) + type?: string; + // (undocumented) + typeMeta?: IndexPatternTypeMeta; + // (undocumented) + version?: string; +} + +// Warning: (ae-missing-release-tag) "IndexPatternsService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class IndexPatternsService { + // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceDeps" needs to be exported by the entry point index.d.ts + constructor({ uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, }: IndexPatternsServiceDeps); + clearCache: (id?: string | undefined) => void; + create(spec: IndexPatternSpec, skipFetchFields?: boolean): Promise; + createAndSave(spec: IndexPatternSpec, override?: boolean, skipFetchFields?: boolean): Promise; + createSavedObject(indexPattern: IndexPattern, override?: boolean): Promise; + delete(indexPatternId: string): Promise<{}>; + // Warning: (ae-forgotten-export) The symbol "EnsureDefaultIndexPattern" needs to be exported by the entry point index.d.ts + // + // (undocumented) + ensureDefaultIndexPattern: EnsureDefaultIndexPattern; + fieldArrayToMap: (fields: FieldSpec[]) => Record; + get: (id: string) => Promise; + // Warning: (ae-forgotten-export) The symbol "IndexPatternSavedObjectAttrs" needs to be exported by the entry point index.d.ts + // + // (undocumented) + getCache: () => Promise[] | null | undefined>; + getDefault: () => Promise; + getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions) => Promise; + // Warning: (ae-forgotten-export) The symbol "GetFieldsOptions" needs to be exported by the entry point index.d.ts + getFieldsForWildcard: (options?: GetFieldsOptions) => Promise; + getIds: (refresh?: boolean) => Promise; + getIdsWithTitle: (refresh?: boolean) => Promise>; + getTitles: (refresh?: boolean) => Promise; + refreshFields: (indexPattern: IndexPattern) => Promise; + savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec; + setDefault: (id: string, force?: boolean) => Promise; + updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number): Promise; +} + // Warning: (ae-missing-release-tag) "TypeMeta" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1381,6 +1415,11 @@ export type InputTimeRange = TimeRange | { to: Moment; }; +// Warning: (ae-missing-release-tag) "isCompleteResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isCompleteResponse: (response?: IKibanaSearchResponse | undefined) => boolean | undefined; + // Warning: (ae-missing-release-tag) "ISearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1389,7 +1428,7 @@ export type ISearch = (request: IKibanaSearchRequest, options?: ISearchOptions) // Warning: (ae-missing-release-tag) "ISearchGeneric" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ISearchGeneric = (request: SearchStrategyRequest, options?: ISearchOptions) => Observable; +export type ISearchGeneric = (request: SearchStrategyRequest, options?: ISearchOptions) => Observable; // Warning: (ae-missing-release-tag) "ISearchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1433,6 +1472,11 @@ export interface ISearchStartSearchSource { createEmpty: () => ISearchSource; } +// Warning: (ae-missing-release-tag) "isErrorResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isErrorResponse: (response?: IKibanaSearchResponse | undefined) => boolean | undefined; + // Warning: (ae-missing-release-tag) "isFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1443,6 +1487,11 @@ export const isFilter: (x: unknown) => x is Filter; // @public (undocumented) export const isFilters: (x: unknown) => x is Filter[]; +// Warning: (ae-missing-release-tag) "isPartialResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isPartialResponse: (response?: IKibanaSearchResponse | undefined) => boolean | undefined; + // Warning: (ae-missing-release-tag) "isQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1676,11 +1725,54 @@ export interface QueryStateChange extends QueryStateChangePartial { globalFilters?: boolean; } -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "QueryStringInput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const QueryStringInput: React.FC>; +export const QueryStringInput: React.FC; + +// Warning: (ae-missing-release-tag) "QueryStringInputProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface QueryStringInputProps { + // (undocumented) + bubbleSubmitEvent?: boolean; + // (undocumented) + className?: string; + // (undocumented) + dataTestSubj?: string; + // (undocumented) + disableAutoFocus?: boolean; + // (undocumented) + indexPatterns: Array; + // (undocumented) + isInvalid?: boolean; + // (undocumented) + languageSwitcherPopoverAnchorPosition?: PopoverAnchorPosition; + // (undocumented) + onBlur?: () => void; + // (undocumented) + onChange?: (query: Query) => void; + // (undocumented) + onChangeQueryInputFocus?: (isFocused: boolean) => void; + // (undocumented) + onSubmit?: (query: Query) => void; + // Warning: (ae-forgotten-export) The symbol "PersistedLog" needs to be exported by the entry point index.d.ts + // + // (undocumented) + persistedLog?: PersistedLog; + // (undocumented) + placeholder?: string; + // (undocumented) + prepend?: any; + // (undocumented) + query: Query; + // (undocumented) + screenTitle?: string; + // Warning: (ae-forgotten-export) The symbol "SuggestionsListSize" needs to be exported by the entry point index.d.ts + // + // (undocumented) + size?: SuggestionsListSize; +} // @public (undocumented) export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; @@ -1936,8 +2028,8 @@ export class SearchInterceptor { // @internal protected pendingCount$: BehaviorSubject; // @internal (undocumented) - protected runSearch(request: IEsSearchRequest, signal: AbortSignal, strategy?: string): Observable; - search(request: IEsSearchRequest, options?: ISearchOptions): Observable; + protected runSearch(request: IEsSearchRequest, signal: AbortSignal, strategy?: string): Observable; + search(request: IEsSearchRequest, options?: ISearchOptions): Observable; // @internal (undocumented) protected setupAbortSignal({ abortSignal, timeout, }: { abortSignal?: AbortSignal; @@ -2189,6 +2281,7 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/meta_filter.ts:54:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:70:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:98:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:67:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts @@ -2217,27 +2310,27 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 888e12a4285b1..802ee6db9433e 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -35,7 +35,7 @@ import { getCombinedSignal, AbortError, IEsSearchRequest, - IEsSearchResponse, + IKibanaSearchResponse, ISearchOptions, ES_SEARCH_STRATEGY, } from '../../common'; @@ -91,7 +91,7 @@ export class SearchInterceptor { request: IEsSearchRequest, signal: AbortSignal, strategy?: string - ): Observable { + ): Observable { const { id, ...searchRequest } = request; const path = trimEnd(`/internal/search/${strategy || ES_SEARCH_STRATEGY}/${id || ''}`, '/'); const body = JSON.stringify(searchRequest); @@ -113,7 +113,7 @@ export class SearchInterceptor { public search( request: IEsSearchRequest, options?: ISearchOptions - ): Observable { + ): Observable { // Defer the following logic until `subscribe` is actually called return defer(() => { if (options?.abortSignal?.aborted) { diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index 35b1bc50ddb1e..299b9d2681578 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -20,7 +20,7 @@ export { SuggestionsComponent } from './typeahead'; export { IndexPatternSelect } from './index_pattern_select'; export { FilterBar } from './filter_bar'; -export { QueryStringInput } from './query_string_input/query_string_input'; +export { QueryStringInput, QueryStringInputProps } from './query_string_input/query_string_input'; export { SearchBar, SearchBarProps, StatefulSearchBarProps } from './search_bar'; // @internal diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 8e1151b387fee..bbd998c0b74de 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -46,8 +46,7 @@ import { PersistedLog, getQueryLog, matchPairs, toUser, fromUser } from '../../q import { SuggestionsListSize } from '../typeahead/suggestions_component'; import { SuggestionsComponent } from '..'; -interface Props { - kibana: KibanaReactContextValue; +export interface QueryStringInputProps { indexPatterns: Array; query: Query; disableAutoFocus?: boolean; @@ -67,6 +66,10 @@ interface Props { isInvalid?: boolean; } +interface Props extends QueryStringInputProps { + kibana: KibanaReactContextValue; +} + interface State { isSuggestionsVisible: boolean; index: number | null; @@ -442,7 +445,7 @@ export class QueryStringInputUI extends Component { // 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_telemetry', { + this.services.http.post('/api/kibana/kql_opt_in_stats', { body: JSON.stringify({ opt_in: language === 'kuery' }), }); @@ -687,4 +690,4 @@ export class QueryStringInputUI extends Component { } } -export const QueryStringInput = withKibana(QueryStringInputUI); +export const QueryStringInput: React.FC = withKibana(QueryStringInputUI); diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 03baff4910309..43080cc5a5989 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -133,16 +133,17 @@ export { IndexPatternsFetcher, FieldDescriptor as IndexPatternFieldDescriptor, shouldReadFieldFromDocValues, // used only in logstash_fields fixture + FieldDescriptor, } from './index_patterns'; export { - IIndexPattern, IFieldType, IFieldSubType, ES_FIELD_TYPES, KBN_FIELD_TYPES, IndexPatternAttributes, UI_SETTINGS, + IndexPattern, } from '../common'; /** @@ -289,3 +290,5 @@ export const config: PluginConfigDescriptor = { }, schema: configSchema, }; + +export type { IndexPatternsService } from './index_patterns'; diff --git a/src/plugins/data/server/index_patterns/utils.ts b/src/plugins/data/server/index_patterns/utils.ts index e841097fe49c2..1e7a85599612c 100644 --- a/src/plugins/data/server/index_patterns/utils.ts +++ b/src/plugins/data/server/index_patterns/utils.ts @@ -18,11 +18,11 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { IIndexPattern, IFieldType } from '../../common'; +import { IFieldType, IndexPatternAttributes, SavedObject } from '../../common'; export const getFieldByName = ( fieldName: string, - indexPattern: IIndexPattern + indexPattern: SavedObject ): IFieldType | undefined => { const fields: IFieldType[] = indexPattern && JSON.parse(indexPattern.attributes.fields); const field = fields && fields.find((f) => f.name === fieldName); @@ -33,8 +33,8 @@ export const getFieldByName = ( export const findIndexPatternById = async ( savedObjectsClient: SavedObjectsClientContract, index: string -): Promise => { - const savedObjectsResponse = await savedObjectsClient.find({ +): Promise | undefined> => { + const savedObjectsResponse = await savedObjectsClient.find({ type: 'index-pattern', fields: ['fields'], search: `"${index}"`, @@ -42,6 +42,6 @@ export const findIndexPatternById = async ( }); if (savedObjectsResponse.total > 0) { - return (savedObjectsResponse.saved_objects[0] as unknown) as IIndexPattern; + return savedObjectsResponse.saved_objects[0]; } }; diff --git a/src/plugins/data/server/kql_telemetry/route.ts b/src/plugins/data/server/kql_telemetry/route.ts index dd7ff333e6257..efcb3d038bcc6 100644 --- a/src/plugins/data/server/kql_telemetry/route.ts +++ b/src/plugins/data/server/kql_telemetry/route.ts @@ -27,7 +27,7 @@ export function registerKqlTelemetryRoute( ) { router.post( { - path: '/api/kibana/kql_opt_in_telemetry', + path: '/api/kibana/kql_opt_in_stats', validate: { body: schema.object({ opt_in: schema.boolean(), diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index b5d5ec283767d..492ad4395b32a 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -22,7 +22,6 @@ import { IRouter } from 'src/core/server'; import { getRequestAbortedSignal } from '../../lib'; import { SearchRouteDependencies } from '../search_service'; import { shimHitsTotal } from './shim_hits_total'; -import { isEsResponse } from '../../../common'; export function registerSearchRoute( router: IRouter, @@ -62,11 +61,9 @@ export function registerSearchRoute( return res.ok({ body: { ...response, - ...(isEsResponse(response) - ? { - rawResponse: shimHitsTotal(response.rawResponse), - } - : {}), + ...{ + rawResponse: shimHitsTotal(response.rawResponse), + }, }, }); } catch (err) { diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index e19d3dd8a5451..90da8c5653ac1 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -40,12 +40,15 @@ import { UsageCollectionSetup } from '../../../usage_collection/server'; import { registerUsageCollector } from './collectors/register'; import { usageProvider } from './collectors/usage'; import { searchTelemetry } from '../saved_objects'; -import { IEsSearchRequest, IEsSearchResponse, ISearchOptions } from '../../common'; +import { + IKibanaSearchRequest, + IKibanaSearchResponse, + IEsSearchRequest, + IEsSearchResponse, + ISearchOptions, +} from '../../common'; -type StrategyMap< - SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse -> = Record>; +type StrategyMap = Record>; /** @internal */ export interface SearchServiceSetupDependencies { @@ -67,7 +70,7 @@ export interface SearchRouteDependencies { export class SearchService implements Plugin { private readonly aggsService = new AggsService(); private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY; - private searchStrategies: StrategyMap = {}; + private searchStrategies: StrategyMap = {}; constructor( private initializerContext: PluginInitializerContext, @@ -113,19 +116,6 @@ export class SearchService implements Plugin { usage, }; } - - private search( - context: RequestHandlerContext, - searchRequest: IEsSearchRequest, - options: ISearchOptions - ) { - return this.getSearchStrategy(options.strategy || this.defaultSearchStrategyName).search( - context, - searchRequest, - options - ); - } - public start( { uiSettings }: CoreStart, { fieldFormats }: SearchServiceStartDependencies @@ -135,7 +125,7 @@ export class SearchService implements Plugin { getSearchStrategy: this.getSearchStrategy, search: ( context: RequestHandlerContext, - searchRequest: IEsSearchRequest, + searchRequest: IKibanaSearchRequest, options: Record ) => { return this.search(context, searchRequest, options); @@ -148,8 +138,8 @@ export class SearchService implements Plugin { } private registerSearchStrategy = < - SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse + SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( name: string, strategy: ISearchStrategy @@ -158,7 +148,25 @@ export class SearchService implements Plugin { this.searchStrategies[name] = strategy; }; - private getSearchStrategy = (name: string): ISearchStrategy => { + private search = < + SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse + >( + context: RequestHandlerContext, + searchRequest: SearchStrategyRequest, + options: ISearchOptions + ): Promise => { + return this.getSearchStrategy( + options.strategy || this.defaultSearchStrategyName + ).search(context, searchRequest, options); + }; + + private getSearchStrategy = < + SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse + >( + name: string + ): ISearchStrategy => { this.logger.debug(`Get strategy ${name}`); const strategy = this.searchStrategies[name]; if (!strategy) { diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index aefdac2ab639f..4764bd77278ac 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -18,7 +18,7 @@ */ import { RequestHandlerContext } from '../../../../core/server'; -import { ISearchOptions } from '../../common/search'; +import { ISearchOptions, IKibanaSearchRequest, IKibanaSearchResponse } from '../../common/search'; import { AggsSetup, AggsStart } from './aggs'; import { SearchUsage } from './collectors'; import { IEsSearchRequest, IEsSearchResponse } from './es_search'; @@ -34,8 +34,8 @@ export interface ISearchSetup { * strategies. */ registerSearchStrategy: < - SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse + SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( name: string, strategy: ISearchStrategy @@ -53,8 +53,8 @@ export interface ISearchSetup { } export interface ISearchStart< - SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse + SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse > { aggs: AggsStart; /** @@ -66,9 +66,9 @@ export interface ISearchStart< ) => ISearchStrategy; search: ( context: RequestHandlerContext, - request: IEsSearchRequest, + request: SearchStrategyRequest, options: ISearchOptions - ) => Promise; + ) => Promise; } /** @@ -76,8 +76,8 @@ export interface ISearchStart< * that resolves to a response. */ export interface ISearchStrategy< - SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse + SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse > { search: ( context: RequestHandlerContext, diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 2024e9e7f2974..9cf7234c4a9ff 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -36,6 +36,7 @@ import { ConfigDeprecationProvider } from '@kbn/config'; import { CoreSetup } from 'src/core/server'; import { CoreSetup as CoreSetup_2 } from 'kibana/server'; import { CoreStart } from 'src/core/server'; +import { CoreStart as CoreStart_2 } from 'kibana/server'; import { CountParams } from 'elasticsearch'; import { CreateDocumentParams } from 'elasticsearch'; import { DeleteDocumentByQueryParams } from 'elasticsearch'; @@ -43,6 +44,7 @@ import { DeleteDocumentParams } from 'elasticsearch'; import { DeleteScriptParams } from 'elasticsearch'; import { DeleteTemplateParams } from 'elasticsearch'; import { Duration } from 'moment'; +import { ElasticsearchClient as ElasticsearchClient_2 } from 'kibana/server'; import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; import { ErrorToastOptions } from 'src/core/public/notifications'; @@ -126,6 +128,7 @@ import { PackageInfo } from '@kbn/config'; import { PathConfigType } from '@kbn/utils'; import { PingParams } from 'elasticsearch'; import { Plugin as Plugin_2 } from 'src/core/server'; +import { Plugin as Plugin_3 } from 'kibana/server'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/server'; import { PutScriptParams } from 'elasticsearch'; import { PutTemplateParams } from 'elasticsearch'; @@ -396,6 +399,32 @@ export interface EsQueryConfig { queryStringOptions: Record; } +// Warning: (ae-missing-release-tag) "FieldDescriptor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +interface FieldDescriptor { + // (undocumented) + aggregatable: boolean; + // (undocumented) + esTypes: string[]; + // (undocumented) + name: string; + // (undocumented) + readFromDocValues: boolean; + // (undocumented) + searchable: boolean; + // Warning: (ae-forgotten-export) The symbol "FieldSubType" needs to be exported by the entry point index.d.ts + // + // (undocumented) + subType?: FieldSubType; + // (undocumented) + type: string; +} + +export { FieldDescriptor } + +export { FieldDescriptor as IndexPatternFieldDescriptor } + // Warning: (ae-missing-release-tag) "FieldFormatConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -467,6 +496,7 @@ export function getShardTimeout(config: SharedGlobalConfig): { timeout?: undefined; }; +// Warning: (ae-forgotten-export) The symbol "IIndexPattern" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "getTime" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -498,28 +528,20 @@ export type IAggConfigs = AggConfigs; export type IAggType = AggType; // Warning: (ae-forgotten-export) The symbol "IKibanaSearchRequest" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "ISearchRequestParams" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "IEsSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IEsSearchRequest extends IKibanaSearchRequest { +export interface IEsSearchRequest extends IKibanaSearchRequest { // (undocumented) indexType?: string; - // Warning: (ae-forgotten-export) The symbol "ISearchRequestParams" needs to be exported by the entry point index.d.ts - // - // (undocumented) - params?: ISearchRequestParams; } // Warning: (ae-forgotten-export) The symbol "IKibanaSearchResponse" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "IEsSearchResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IEsSearchResponse extends IKibanaSearchResponse { - isPartial?: boolean; - isRunning?: boolean; - // (undocumented) - rawResponse: SearchResponse; -} +export type IEsSearchResponse = IKibanaSearchResponse>; // Warning: (ae-missing-release-tag) "IFieldFormatsRegistry" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -590,40 +612,129 @@ export interface IFieldType { visualizable?: boolean; } -// Warning: (ae-missing-release-tag) "IIndexPattern" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-forgotten-export) The symbol "MetricAggType" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "IMetricAggType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type IMetricAggType = MetricAggType; + +// Warning: (ae-missing-release-tag) "IndexPattern" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IIndexPattern { +export class IndexPattern implements IIndexPattern { + // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts + constructor({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); + addScriptedField(name: string, script: string, fieldType?: string, lang?: string): Promise; // (undocumented) - [key: string]: any; + fieldFormatMap: Record; + // Warning: (ae-forgotten-export) The symbol "IIndexPatternFieldList" needs to be exported by the entry point index.d.ts + // + // (undocumented) + fields: IIndexPatternFieldList & { + toSpec: () => IndexPatternFieldMap; + }; // (undocumented) - fieldFormatMap?: Record; + flattenHit: (hit: Record, deep?: boolean) => Record; // (undocumented) - fields: IFieldType[]; + formatField: FormatFieldFn; // (undocumented) - getTimeField?(): IFieldType | undefined; + formatHit: { + (hit: Record, type?: string): any; + formatField: FormatFieldFn; + }; + // (undocumented) + getAggregationRestrictions(): Record> | undefined; + getAsSavedObjectBody(): { + title: string; + timeFieldName: string | undefined; + intervalName: string | undefined; + sourceFilters: string | undefined; + fields: string | undefined; + fieldFormatMap: string | undefined; + type: string | undefined; + typeMeta: string | undefined; + }; + // (undocumented) + getComputedFields(): { + storedFields: string[]; + scriptFields: any; + docvalueFields: { + field: any; + format: string; + }[]; + }; + // (undocumented) + getFieldByName(name: string): IndexPatternField | undefined; + getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; + // Warning: (ae-forgotten-export) The symbol "IndexPatternField" needs to be exported by the entry point index.d.ts + // + // (undocumented) + getNonScriptedFields(): IndexPatternField[]; + getOriginalSavedObjectBody: () => { + title?: string | undefined; + timeFieldName?: string | undefined; + intervalName?: string | undefined; + fields?: string | undefined; + sourceFilters?: string | undefined; + fieldFormatMap?: string | undefined; + typeMeta?: string | undefined; + type?: string | undefined; + }; + // (undocumented) + getScriptedFields(): IndexPatternField[]; + getSourceFiltering(): { + excludes: any[]; + }; + // (undocumented) + getTimeField(): IndexPatternField | undefined; // (undocumented) id?: string; // (undocumented) - timeFieldName?: string; + intervalName: string | undefined; + // (undocumented) + isTimeBased(): boolean; + // (undocumented) + isTimeBasedWildcard(): boolean; + // (undocumented) + isTimeNanosBased(): boolean; + // (undocumented) + metaFields: string[]; + // (undocumented) + popularizeField(fieldName: string, unit?: number): Promise; + removeScriptedField(fieldName: string): void; + resetOriginalSavedObjectBody: () => void; + // Warning: (ae-forgotten-export) The symbol "SourceFilter" needs to be exported by the entry point index.d.ts + // + // (undocumented) + sourceFilters?: SourceFilter[]; + // (undocumented) + timeFieldName: string | undefined; // (undocumented) title: string; + // Warning: (ae-forgotten-export) The symbol "IndexPatternSpec" needs to be exported by the entry point index.d.ts + // + // (undocumented) + toSpec(): IndexPatternSpec; + // (undocumented) + type: string | undefined; + // Warning: (ae-forgotten-export) The symbol "TypeMeta" needs to be exported by the entry point index.d.ts + // + // (undocumented) + typeMeta?: TypeMeta; // (undocumented) - type?: string; + version: string | undefined; } -// Warning: (ae-forgotten-export) The symbol "MetricAggType" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "IMetricAggType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type IMetricAggType = MetricAggType; - // Warning: (ae-missing-release-tag) "IndexPatternAttributes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public @deprecated +// @public (undocumented) export interface IndexPatternAttributes { // (undocumented) fieldFormatMap?: string; @@ -643,28 +754,6 @@ export interface IndexPatternAttributes { typeMeta: string; } -// Warning: (ae-missing-release-tag) "FieldDescriptor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface IndexPatternFieldDescriptor { - // (undocumented) - aggregatable: boolean; - // (undocumented) - esTypes: string[]; - // (undocumented) - name: string; - // (undocumented) - readFromDocValues: boolean; - // (undocumented) - searchable: boolean; - // Warning: (ae-forgotten-export) The symbol "FieldSubType" needs to be exported by the entry point index.d.ts - // - // (undocumented) - subType?: FieldSubType; - // (undocumented) - type: string; -} - // Warning: (ae-missing-release-tag) "indexPatterns" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -683,14 +772,29 @@ export class IndexPatternsFetcher { metaFields: string[]; lookBack: number; interval: string; - }): Promise; + }): Promise; getFieldsForWildcard(options: { pattern: string | string[]; metaFields?: string[]; fieldCapsOptions?: { allowNoIndices: boolean; }; - }): Promise; + }): Promise; +} + +// Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStart" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "IndexPatternsService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class IndexPatternsService implements Plugin_3 { + // (undocumented) + setup(core: CoreSetup_2): void; + // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStartDeps" needs to be exported by the entry point index.d.ts + // + // (undocumented) + start(core: CoreStart_2, { fieldFormats, logger }: IndexPatternsServiceStartDeps): { + indexPatternsServiceFactory: (kibanaRequest: KibanaRequest) => Promise; + }; } // Warning: (ae-missing-release-tag) "ISearchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -713,14 +817,14 @@ export interface ISearchSetup { // // (undocumented) aggs: AggsSetup; - registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void; + registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void; usage?: SearchUsage; } // Warning: (ae-missing-release-tag) "ISearchStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ISearchStart { +export interface ISearchStart { // Warning: (ae-forgotten-export) The symbol "AggsStart" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -729,13 +833,13 @@ export interface ISearchStart Promise; + search: (context: RequestHandlerContext, request: SearchStrategyRequest, options: ISearchOptions) => Promise; } // Warning: (ae-missing-release-tag) "ISearchStrategy" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export interface ISearchStrategy { +export interface ISearchStrategy { // (undocumented) cancel?: (context: RequestHandlerContext, id: string) => Promise; // (undocumented) @@ -892,7 +996,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; + indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; }; }; // (undocumented) @@ -926,8 +1030,6 @@ export interface PluginStart { // // (undocumented) fieldFormats: FieldFormatsStart; - // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStart" needs to be exported by the entry point index.d.ts - // // (undocumented) indexPatterns: IndexPatternsServiceStart; // (undocumented) @@ -1113,7 +1215,8 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // // src/plugins/data/common/es_query/filters/meta_filter.ts:53:3 - (ae-forgotten-export) The symbol "FilterState" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/meta_filter.ts:54:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/fields/types.ts:41:25 - (ae-forgotten-export) The symbol "IndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:45 - (ae-forgotten-export) The symbol "IndexPatternFieldMap" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:70:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts @@ -1135,19 +1238,20 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:227:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:228:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:237:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:238:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:239:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:243:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:244:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:248:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:251:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:226:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:226:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:226:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:226:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:228:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:229:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:238:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:240:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:244:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:245:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:249:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:252:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index_patterns/index_patterns_service.ts:50:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:88:66 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/discover/public/application/angular/context.js b/src/plugins/discover/public/application/angular/context.js index 6223090aa9f97..bb9d71c8671a2 100644 --- a/src/plugins/discover/public/application/angular/context.js +++ b/src/plugins/discover/public/application/angular/context.js @@ -45,26 +45,18 @@ const k7Breadcrumbs = ($route) => { }; getAngularModule().config(($routeProvider) => { - $routeProvider - // deprecated route, kept for compatibility - // should be removed in the future - .when('/context/:indexPatternId/:type/:id*', { - redirectTo: '/context/:indexPatternId/:id', - }) - .when('/context/:indexPatternId/:id*', { - controller: ContextAppRouteController, - k7Breadcrumbs, - controllerAs: 'contextAppRoute', - resolve: { - indexPattern: ($route, Promise) => { - const indexPattern = getServices().indexPatterns.get( - $route.current.params.indexPatternId - ); - return Promise.props({ ip: indexPattern }); - }, + $routeProvider.when('/context/:indexPatternId/:id*', { + controller: ContextAppRouteController, + k7Breadcrumbs, + controllerAs: 'contextAppRoute', + resolve: { + indexPattern: ($route, Promise) => { + const indexPattern = getServices().indexPatterns.get($route.current.params.indexPatternId); + return Promise.props({ ip: indexPattern }); }, - template: contextAppRouteTemplate, - }); + }, + template: contextAppRouteTemplate, + }); }); function ContextAppRouteController($routeParams, $scope, $route) { diff --git a/src/plugins/discover/public/application/components/discover_legacy.tsx b/src/plugins/discover/public/application/components/discover_legacy.tsx index 1a98843649259..9c3d833d73b23 100644 --- a/src/plugins/discover/public/application/components/discover_legacy.tsx +++ b/src/plugins/discover/public/application/components/discover_legacy.tsx @@ -25,7 +25,7 @@ import { IUiSettingsClient, MountPoint } from 'kibana/public'; import { HitsCounter } from './hits_counter'; import { TimechartHeader } from './timechart_header'; import { DiscoverSidebar } from './sidebar'; -import { getServices, IIndexPattern } from '../../kibana_services'; +import { getServices, IndexPattern } from '../../kibana_services'; // @ts-ignore import { DiscoverNoResults } from '../angular/directives/no_results'; import { DiscoverUninitialized } from '../angular/directives/uninitialized'; @@ -58,7 +58,7 @@ export interface DiscoverLegacyProps { fieldCounts: Record; histogramData: Chart; hits: number; - indexPattern: IIndexPattern; + indexPattern: IndexPattern; minimumVisibleRows: number; onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; onChangeInterval: (interval: string) => void; diff --git a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts index 8746883a5d968..dad208c815675 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts @@ -142,7 +142,7 @@ describe('fieldCalculator', function () { let hits: any; beforeEach(function () { - hits = _.each(_.cloneDeep(realHits), indexPattern.flattenHit); + hits = _.each(_.cloneDeep(realHits), (hit) => indexPattern.flattenHit(hit)); }); it('Should return an array of values for _source fields', function () { diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx index 07e9e0a129a26..2874e2483275b 100644 --- a/src/plugins/discover/public/application/components/table/table.test.tsx +++ b/src/plugins/discover/public/application/components/table/table.test.tsx @@ -22,7 +22,7 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { DocViewTable } from './table'; import { indexPatterns, IndexPattern } from '../../../../../data/public'; -const indexPattern = { +const indexPattern = ({ fields: { getAll: () => [ { @@ -60,7 +60,7 @@ const indexPattern = { metaFields: ['_index', '_score'], flattenHit: undefined, formatHit: jest.fn((hit) => hit._source), -} as IndexPattern; +} as unknown) as IndexPattern; indexPattern.fields.getByName = (name: string) => { return indexPattern.fields.getAll().find((field) => field.name === name); diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 440bd3fdf86d3..b1bbc89b62d9d 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -277,6 +277,14 @@ export class DiscoverPlugin return `#${path}`; }); plugins.urlForwarding.forwardApp('context', 'discover', (path) => { + const urlParts = path.split('/'); + // take care of urls containing legacy url, those split in the following way + // ["", "context", indexPatternId, _type, id + params] + if (urlParts[4]) { + // remove _type part + const newPath = [...urlParts.slice(0, 3), ...urlParts.slice(4)].join('/'); + return `#${newPath}`; + } return `#${path}`; }); plugins.urlForwarding.forwardApp('discover', 'discover', (path) => { diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx index 8c3d7ab9c30d0..ba24913c6d1c0 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx @@ -18,7 +18,7 @@ */ import { EditPanelAction } from './edit_panel_action'; -import { Embeddable, EmbeddableInput } from '../embeddables'; +import { Embeddable, EmbeddableInput, SavedObjectEmbeddableInput } from '../embeddables'; import { ViewMode } from '../types'; import { ContactCardEmbeddable } from '../test_samples'; import { embeddablePluginMock } from '../../mocks'; @@ -53,20 +53,50 @@ test('is compatible when edit url is available, in edit mode and editable', asyn ).toBe(true); }); -test('redirects to app using state transfer', async () => { +test('redirects to app using state transfer with by value mode', async () => { applicationMock.currentAppId$ = of('superCoolCurrentApp'); const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock); - const input = { id: '123', viewMode: ViewMode.EDIT }; - const embeddable = new EditableEmbeddable(input, true); + const embeddable = new EditableEmbeddable( + ({ + id: '123', + viewMode: ViewMode.EDIT, + coolInput1: 1, + coolInput2: 2, + } as unknown) as EmbeddableInput, + true + ); + embeddable.getOutput = jest.fn(() => ({ editApp: 'ultraVisualize', editPath: '/123' })); + await action.execute({ embeddable }); + expect(stateTransferMock.navigateToEditor).toHaveBeenCalledWith('ultraVisualize', { + path: '/123', + state: { + originatingApp: 'superCoolCurrentApp', + embeddableId: '123', + valueInput: { + id: '123', + viewMode: ViewMode.EDIT, + coolInput1: 1, + coolInput2: 2, + }, + }, + }); +}); + +test('redirects to app using state transfer without by value mode', async () => { + applicationMock.currentAppId$ = of('superCoolCurrentApp'); + const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock); + const embeddable = new EditableEmbeddable( + { id: '123', viewMode: ViewMode.EDIT, savedObjectId: '1234' } as SavedObjectEmbeddableInput, + true + ); embeddable.getOutput = jest.fn(() => ({ editApp: 'ultraVisualize', editPath: '/123' })); await action.execute({ embeddable }); expect(stateTransferMock.navigateToEditor).toHaveBeenCalledWith('ultraVisualize', { path: '/123', state: { originatingApp: 'superCoolCurrentApp', - byValueMode: true, embeddableId: '123', - valueInput: input, + valueInput: undefined, }, }); }); diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index 8d12ddd0299e7..cbc28713197ba 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -29,6 +29,8 @@ import { EmbeddableEditorState, EmbeddableStateTransfer, SavedObjectEmbeddableInput, + EmbeddableInput, + Container, } from '../..'; export const ACTION_EDIT_PANEL = 'editPanel'; @@ -118,8 +120,7 @@ export class EditPanelAction implements Action { const byValueMode = !(embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId; const state: EmbeddableEditorState = { originatingApp: this.currentAppId, - byValueMode, - valueInput: byValueMode ? embeddable.getInput() : undefined, + valueInput: byValueMode ? this.getExplicitInput({ embeddable }) : undefined, embeddableId: embeddable.id, }; return { app, path, state }; @@ -132,4 +133,11 @@ export class EditPanelAction implements Action { const editUrl = embeddable ? embeddable.getOutput().editUrl : undefined; return editUrl ? editUrl : ''; } + + private getExplicitInput({ embeddable }: ActionContext): EmbeddableInput { + return ( + (embeddable.getRoot() as Container)?.getInput()?.panels?.[embeddable.id]?.explicitInput ?? + embeddable.getInput() + ); + } } diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index 38975cc220bc2..9f701f021162a 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -199,8 +199,8 @@ export abstract class Container< return { type: factory.type, explicitInput: { - id: embeddableId, ...explicitInput, + id: embeddableId, } as TEmbeddableInput, }; } diff --git a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx index 715827a72c61b..17a2ac3b2a32b 100644 --- a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx @@ -17,43 +17,36 @@ * under the License. */ import React from 'react'; +import { wait } from '@testing-library/dom'; +import { cleanup, render } from '@testing-library/react/pure'; import { ErrorEmbeddable } from './error_embeddable'; import { EmbeddableRoot } from './embeddable_root'; -import { mount } from 'enzyme'; + +afterEach(cleanup); test('ErrorEmbeddable renders an embeddable', async () => { const embeddable = new ErrorEmbeddable('some error occurred', { id: '123', title: 'Error' }); - const component = mount(); - expect( - component.getDOMNode().querySelectorAll('[data-test-subj="embeddableStackError"]').length - ).toBe(1); - expect( - component.getDOMNode().querySelectorAll('[data-test-subj="errorMessageMarkdown"]').length - ).toBe(1); - expect( - component - .getDOMNode() - .querySelectorAll('[data-test-subj="errorMessageMarkdown"]')[0] - .innerHTML.includes('some error occurred') - ).toBe(true); + const { getByTestId, getByText } = render(); + + expect(getByTestId('embeddableStackError')).toBeVisible(); + await wait(() => getByTestId('errorMessageMarkdown')); // wait for lazy markdown component + expect(getByText(/some error occurred/i)).toBeVisible(); }); test('ErrorEmbeddable renders an embeddable with markdown message', async () => { const error = '[some link](http://localhost:5601/takeMeThere)'; const embeddable = new ErrorEmbeddable(error, { id: '123', title: 'Error' }); - const component = mount(); - expect( - component.getDOMNode().querySelectorAll('[data-test-subj="embeddableStackError"]').length - ).toBe(1); - expect( - component.getDOMNode().querySelectorAll('[data-test-subj="errorMessageMarkdown"]').length - ).toBe(1); - expect( - component - .getDOMNode() - .querySelectorAll('[data-test-subj="errorMessageMarkdown"]')[0] - .innerHTML.includes( - 'some link' - ) - ).toBe(true); + const { getByTestId, getByText } = render(); + + expect(getByTestId('embeddableStackError')).toBeVisible(); + await wait(() => getByTestId('errorMessageMarkdown')); // wait for lazy markdown component + expect(getByText(/some link/i)).toMatchInlineSnapshot(` + + some link + + `); }); diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts index ef79b18acd4f5..4155cb4d3b60c 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts @@ -85,10 +85,10 @@ describe('embeddable state transfer', () => { it('can send an outgoing embeddable package state', async () => { await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { - state: { type: 'coolestType', id: '150' }, + state: { type: 'coolestType', input: { savedObjectId: '150' } }, }); expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { - state: { type: 'coolestType', id: '150' }, + state: { type: 'coolestType', input: { savedObjectId: '150' } }, }); }); @@ -96,12 +96,16 @@ describe('embeddable state transfer', () => { const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' }); stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { - state: { type: 'coolestType', id: '150' }, + state: { type: 'coolestType', input: { savedObjectId: '150' } }, appendToExistingState: true, }); expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { path: undefined, - state: { kibanaIsNowForSports: 'extremeSportsKibana', type: 'coolestType', id: '150' }, + state: { + kibanaIsNowForSports: 'extremeSportsKibana', + type: 'coolestType', + input: { savedObjectId: '150' }, + }, }); }); @@ -120,10 +124,13 @@ describe('embeddable state transfer', () => { }); it('can fetch an incoming embeddable package state', async () => { - const historyMock = mockHistoryState({ type: 'skisEmbeddable', id: '123' }); + const historyMock = mockHistoryState({ + type: 'skisEmbeddable', + input: { savedObjectId: '123' }, + }); stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); const fetchedState = stateTransfer.getIncomingEmbeddablePackage(); - expect(fetchedState).toEqual({ type: 'skisEmbeddable', id: '123' }); + expect(fetchedState).toEqual({ type: 'skisEmbeddable', input: { savedObjectId: '123' } }); }); it('returns undefined when embeddable package is not in the right shape', async () => { @@ -136,12 +143,12 @@ describe('embeddable state transfer', () => { it('removes all keys in the keysToRemoveAfterFetch array', async () => { const historyMock = mockHistoryState({ type: 'skisEmbeddable', - id: '123', + input: { savedObjectId: '123' }, test1: 'test1', test2: 'test2', }); stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); - stateTransfer.getIncomingEmbeddablePackage({ keysToRemoveAfterFetch: ['type', 'id'] }); + stateTransfer.getIncomingEmbeddablePackage({ keysToRemoveAfterFetch: ['type', 'input'] }); expect(historyMock.replace).toHaveBeenCalledWith( expect.objectContaining({ state: { test1: 'test1', test2: 'test2' } }) ); @@ -150,7 +157,7 @@ describe('embeddable state transfer', () => { it('leaves state as is when no keysToRemove are supplied', async () => { const historyMock = mockHistoryState({ type: 'skisEmbeddable', - id: '123', + input: { savedObjectId: '123' }, test1: 'test1', test2: 'test2', }); @@ -158,7 +165,7 @@ describe('embeddable state transfer', () => { stateTransfer.getIncomingEmbeddablePackage(); expect(historyMock.location.state).toEqual({ type: 'skisEmbeddable', - id: '123', + input: { savedObjectId: '123' }, test1: 'test1', test2: 'test2', }); diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts index 3f3456d914728..d8b4f4801bba3 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/types.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts @@ -17,17 +17,17 @@ * under the License. */ -import { EmbeddableInput } from '..'; +import { Optional } from '@kbn/utility-types'; +import { EmbeddableInput, SavedObjectEmbeddableInput } from '..'; /** - * Represents a state package that contains the last active app id. + * A state package that contains information an editor will need to create or edit an embeddable then redirect back. * @public */ export interface EmbeddableEditorState { originatingApp: string; - byValueMode?: boolean; - valueInput?: EmbeddableInput; embeddableId?: string; + valueInput?: EmbeddableInput; } export function isEmbeddableEditorState(state: unknown): state is EmbeddableEditorState { @@ -35,32 +35,18 @@ export function isEmbeddableEditorState(state: unknown): state is EmbeddableEdit } /** - * Represents a state package that contains all fields necessary to create an embeddable by reference in a container. + * A state package that contains all fields necessary to create or update an embeddable by reference or by value in a container. * @public */ -export interface EmbeddablePackageByReferenceState { +export interface EmbeddablePackageState { type: string; - id: string; -} - -/** - * Represents a state package that contains all fields necessary to create an embeddable by value in a container. - * @public - */ -export interface EmbeddablePackageByValueState { - type: string; - input: EmbeddableInput; + input: Optional | Optional; embeddableId?: string; } -export type EmbeddablePackageState = - | EmbeddablePackageByReferenceState - | EmbeddablePackageByValueState; - export function isEmbeddablePackageState(state: unknown): state is EmbeddablePackageState { return ( - (ensureFieldOfTypeExists('type', state, 'string') && - ensureFieldOfTypeExists('id', state, 'string')) || + ensureFieldOfTypeExists('type', state, 'string') && ensureFieldOfTypeExists('input', state, 'object') ); } diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/use_xjson_mode.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/use_xjson_mode.ts deleted file mode 100644 index b783045492f05..0000000000000 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/use_xjson_mode.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { XJsonLang } from '@kbn/monaco'; -import { useXJsonMode as useBaseXJsonMode } from '../xjson'; - -interface ReturnValue extends ReturnType { - XJsonLang: typeof XJsonLang; -} - -export const useXJsonMode = (json: Parameters[0]): ReturnValue => { - return { - ...useBaseXJsonMode(json), - XJsonLang, - }; -}; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/index.ts index a9c6ea1e01d54..adbdbe97c4a07 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/index.ts +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/index.ts @@ -18,3 +18,5 @@ */ export { useXJsonMode } from './use_xjson_mode'; + +export { collapseLiteralStrings, expandLiteralStrings } from './json_xjson_translation_tools'; diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts rename to src/plugins/es_ui_shared/__packages_do_not_import__/xjson/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_collapsing.txt b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/json_xjson_translation_tools/__tests__/utils_string_collapsing.txt similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_collapsing.txt rename to src/plugins/es_ui_shared/__packages_do_not_import__/xjson/json_xjson_translation_tools/__tests__/utils_string_collapsing.txt diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/json_xjson_translation_tools/__tests__/utils_string_expanding.txt similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt rename to src/plugins/es_ui_shared/__packages_do_not_import__/xjson/json_xjson_translation_tools/__tests__/utils_string_expanding.txt diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/json_xjson_translation_tools/index.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts rename to src/plugins/es_ui_shared/__packages_do_not_import__/xjson/json_xjson_translation_tools/index.ts diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/parser.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/json_xjson_translation_tools/parser.ts similarity index 100% rename from src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/parser.ts rename to src/plugins/es_ui_shared/__packages_do_not_import__/xjson/json_xjson_translation_tools/parser.ts diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/use_xjson_mode.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/use_xjson_mode.ts index 7dcc7c9ed83bc..1d4c473ed14e4 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/use_xjson_mode.ts +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/xjson/use_xjson_mode.ts @@ -18,7 +18,8 @@ */ import { useState, Dispatch } from 'react'; -import { collapseLiteralStrings, expandLiteralStrings } from '../../public'; + +import { collapseLiteralStrings, expandLiteralStrings } from './json_xjson_translation_tools'; interface ReturnValue { xJson: string; diff --git a/src/plugins/es_ui_shared/kibana.json b/src/plugins/es_ui_shared/kibana.json index eab7355d66f09..d442bfb93d5af 100644 --- a/src/plugins/es_ui_shared/kibana.json +++ b/src/plugins/es_ui_shared/kibana.json @@ -4,7 +4,6 @@ "ui": true, "server": true, "extraPublicDirs": [ - "static/ace_x_json/hooks", "static/validators/string", "static/forms/hook_form_lib", "static/forms/helpers", diff --git a/src/plugins/es_ui_shared/public/console_lang/index.ts b/src/plugins/es_ui_shared/public/console_lang/index.ts deleted file mode 100644 index 7d83191569622..0000000000000 --- a/src/plugins/es_ui_shared/public/console_lang/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// Lib is intentionally not included in this barrel export file to separate worker logic -// from being imported with pure functions - -export { - ElasticsearchSqlHighlightRules, - ScriptHighlightRules, - XJsonHighlightRules, - addXJsonToRules, - XJsonMode, - installXJsonMode, -} from './ace/modes'; - -export { expandLiteralStrings, collapseLiteralStrings } from './lib'; diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index 5a1c13658604a..94b084e7d3f20 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -22,9 +22,9 @@ * In the future, each top level folder should be exported like that to avoid naming collision */ import * as Forms from './forms'; -import * as Monaco from './monaco'; import * as ace from './ace'; import * as GlobalFlyout from './global_flyout'; +import * as XJson from './xjson'; export { JsonEditor, OnJsonEditorUpdateHandler, JsonEditorState } from './components/json_editor'; @@ -43,17 +43,6 @@ export { export { indices } from './indices'; -export { - installXJsonMode, - XJsonMode, - ElasticsearchSqlHighlightRules, - addXJsonToRules, - ScriptHighlightRules, - XJsonHighlightRules, - collapseLiteralStrings, - expandLiteralStrings, -} from './console_lang'; - export { AuthorizationContext, AuthorizationProvider, @@ -66,7 +55,7 @@ export { useAuthorizationContext, } from './authorization'; -export { Monaco, Forms, ace, GlobalFlyout }; +export { Forms, ace, GlobalFlyout, XJson }; export { extractQueryParams } from './url'; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/index.ts b/src/plugins/es_ui_shared/public/xjson/index.ts similarity index 93% rename from src/plugins/es_ui_shared/__packages_do_not_import__/monaco/index.ts rename to src/plugins/es_ui_shared/public/xjson/index.ts index a9c6ea1e01d54..d505cbe0c6348 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/monaco/index.ts +++ b/src/plugins/es_ui_shared/public/xjson/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { useXJsonMode } from './use_xjson_mode'; +export * from '../../__packages_do_not_import__/xjson'; diff --git a/src/plugins/es_ui_shared/static/ace_x_json/hooks/index.ts b/src/plugins/es_ui_shared/static/ace_x_json/hooks/index.ts deleted file mode 100644 index 1d2c33a9f0f47..0000000000000 --- a/src/plugins/es_ui_shared/static/ace_x_json/hooks/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { useXJsonMode } from './use_x_json'; diff --git a/src/plugins/es_ui_shared/static/ace_x_json/hooks/use_x_json.ts b/src/plugins/es_ui_shared/static/ace_x_json/hooks/use_x_json.ts deleted file mode 100644 index 3a093ac6869d0..0000000000000 --- a/src/plugins/es_ui_shared/static/ace_x_json/hooks/use_x_json.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { XJsonMode } from '../../../public'; -import { useXJsonMode as useBaseXJsonMode } from '../../../__packages_do_not_import__/xjson'; - -const xJsonMode = new XJsonMode(); - -interface ReturnValue extends ReturnType { - xJsonMode: typeof xJsonMode; -} - -export const useXJsonMode = (json: Parameters[0]): ReturnValue => { - return { - ...useBaseXJsonMode(json), - xJsonMode, - }; -}; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap index 6cc92d20cfdcc..544e3ba983122 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap @@ -27,7 +27,7 @@ exports[`StepTimeField should render "Custom index pattern ID already exists" wh /> ({ - fieldsFetcher: { - fetchForWildcard: jest.fn().mockReturnValue(Promise.resolve(fields)), - }, - }), + create: () => ({}), + getFieldsForWildcard: jest.fn().mockReturnValue(Promise.resolve(fields)), } as any; describe('StepTimeField', () => { diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx index 5d33a08557fed..cacabb6d7623b 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx @@ -108,12 +108,12 @@ export class StepTimeField extends Component { }); test('invokes the provided services when creating an index pattern', async () => { - const create = jest.fn().mockImplementation(() => 'id'); + const newIndexPatternAndSave = jest.fn().mockImplementation(async () => { + return indexPattern; + }); const clear = jest.fn(); mockContext.data.indexPatterns.clearCache = clear; const indexPattern = ({ @@ -151,11 +153,10 @@ describe('CreateIndexPatternWizard', () => { title: 'my-fake-index-pattern', timeFieldName: 'timestamp', fields: [], - create, + _fetchFields: jest.fn(), } as unknown) as IndexPattern; - mockContext.data.indexPatterns.make = async () => { - return indexPattern; - }; + mockContext.data.indexPatterns.createAndSave = newIndexPatternAndSave; + mockContext.data.indexPatterns.setDefault = jest.fn(); const component = createComponentWithContext( CreateIndexPatternWizard, @@ -165,9 +166,8 @@ describe('CreateIndexPatternWizard', () => { component.setState({ indexPattern: 'foo' }); await (component.instance() as CreateIndexPatternWizard).createIndexPattern(undefined, 'id'); - expect(mockContext.uiSettings.get).toBeCalled(); - expect(create).toBeCalled(); - expect(clear).toBeCalledWith('id'); - expect(routeComponentPropsMock.history.push).toBeCalledWith(`/patterns/id`); + expect(newIndexPatternAndSave).toBeCalled(); + expect(clear).toBeCalledWith('1'); + expect(routeComponentPropsMock.history.push).toBeCalledWith(`/patterns/1`); }); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx index a789ebbfadbce..aa97c21d766b9 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx @@ -40,6 +40,7 @@ import { ensureMinimumTime, getIndices } from './lib'; import { IndexPatternCreationConfig } from '../..'; import { IndexPatternManagmentContextValue } from '../../types'; import { MatchedItem } from './types'; +import { DuplicateIndexPatternError, IndexPattern } from '../../../../data/public'; interface CreateIndexPatternWizardState { step: number; @@ -156,50 +157,50 @@ export class CreateIndexPatternWizard extends Component< }; createIndexPattern = async (timeFieldName: string | undefined, indexPatternId: string) => { + let emptyPattern: IndexPattern; const { history } = this.props; const { indexPattern } = this.state; - const emptyPattern = await this.context.services.data.indexPatterns.make(); - - Object.assign(emptyPattern, { - id: indexPatternId, - title: indexPattern, - timeFieldName, - ...this.state.indexPatternCreationType.getIndexPatternMappings(), - }); - - const createdId = await emptyPattern.create(); - if (!createdId) { - const confirmMessage = i18n.translate( - 'indexPatternManagement.indexPattern.titleExistsLabel', - { - values: { title: emptyPattern.title }, - defaultMessage: "An index pattern with the title '{title}' already exists.", - } - ); - - const isConfirmed = await this.context.services.overlays.openConfirm(confirmMessage, { - confirmButtonText: i18n.translate( - 'indexPatternManagement.indexPattern.goToPatternButtonLabel', + try { + emptyPattern = await this.context.services.data.indexPatterns.createAndSave({ + id: indexPatternId, + title: indexPattern, + timeFieldName, + ...this.state.indexPatternCreationType.getIndexPatternMappings(), + }); + } catch (err) { + if (err instanceof DuplicateIndexPatternError) { + const confirmMessage = i18n.translate( + 'indexPatternManagement.indexPattern.titleExistsLabel', { - defaultMessage: 'Go to existing pattern', + values: { title: emptyPattern!.title }, + defaultMessage: "An index pattern with the title '{title}' already exists.", } - ), - }); + ); + + const isConfirmed = await this.context.services.overlays.openConfirm(confirmMessage, { + confirmButtonText: i18n.translate( + 'indexPatternManagement.indexPattern.goToPatternButtonLabel', + { + defaultMessage: 'Go to existing pattern', + } + ), + }); - if (isConfirmed) { - return history.push(`/patterns/${indexPatternId}`); + if (isConfirmed) { + return history.push(`/patterns/${indexPatternId}`); + } else { + return; + } } else { - return; + throw err; } } - if (!this.context.services.uiSettings.get('defaultIndex')) { - await this.context.services.uiSettings.set('defaultIndex', createdId); - } + await this.context.services.data.indexPatterns.setDefault(emptyPattern.id as string); - this.context.services.data.indexPatterns.clearCache(createdId); - history.push(`/patterns/${createdId}`); + this.context.services.data.indexPatterns.clearCache(emptyPattern.id as string); + history.push(`/patterns/${emptyPattern.id}`); }; goToTimeFieldStep = (indexPattern: string, selectedTimeField?: string) => { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx index 13be9ca6c9c25..08edf42df60d8 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx @@ -96,7 +96,7 @@ export const CreateEditField = withRouter( indexPattern={indexPattern} spec={spec} services={{ - saveIndexPattern: data.indexPatterns.save.bind(data.indexPatterns), + saveIndexPattern: data.indexPatterns.updateSavedObject.bind(data.indexPatterns), redirectAway, }} /> diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx index d09836019b0bc..67a20c428040f 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx @@ -121,7 +121,8 @@ export const EditIndexPattern = withRouter( const refreshFields = () => { overlays.openConfirm(confirmMessage, confirmModalOptionsRefresh).then(async (isConfirmed) => { if (isConfirmed) { - await indexPattern.refreshFields(); + await data.indexPatterns.refreshFields(indexPattern); + await data.indexPatterns.updateSavedObject(indexPattern); setFields(indexPattern.getNonScriptedFields()); } }); @@ -236,7 +237,7 @@ export const EditIndexPattern = withRouter( ({ @@ -43,7 +43,7 @@ const helpers = { const indexPattern = ({ getNonScriptedFields: () => fields, -} as unknown) as IIndexPattern; +} as unknown) as IndexPattern; const mockFieldToIndexPatternField = (spec: Record) => { return new IndexPatternField( diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 23977aac7fa7a..7be420e2af50d 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -19,23 +19,19 @@ import React, { Component } from 'react'; import { createSelector } from 'reselect'; -import { - IndexPatternField, - IIndexPattern, - IFieldType, -} from '../../../../../../plugins/data/public'; +import { IndexPatternField, IndexPattern, IFieldType } from '../../../../../../plugins/data/public'; import { Table } from './components/table'; import { getFieldFormat } from './lib'; import { IndexedFieldItem } from './types'; interface IndexedFieldsTableProps { fields: IndexPatternField[]; - indexPattern: IIndexPattern; + indexPattern: IndexPattern; fieldFilter?: string; indexedFieldTypeFilter?: string; helpers: { redirectToRoute: (obj: any) => void; - getFieldInfo: (indexPattern: IIndexPattern, field: IFieldType) => string[]; + getFieldInfo: (indexPattern: IndexPattern, field: IFieldType) => string[]; }; fieldWildcardMatcher: (filters: any[]) => (val: any) => boolean; } diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx index 08cc90faf75fa..c7ea20c700086 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx @@ -39,7 +39,7 @@ interface ScriptedFieldsTableProps { }; onRemoveField?: () => void; painlessDocLink: string; - saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject']; } interface ScriptedFieldsTableState { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx index e43ee2e55eeca..2d3a61b42c3a4 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx @@ -22,13 +22,13 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Table, TableProps, TableState } from './table'; import { EuiTableFieldDataColumnType, keys } from '@elastic/eui'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { IndexPattern } from 'src/plugins/data/public'; import { SourceFiltersTableFilter } from '../../types'; -const indexPattern = {} as IIndexPattern; +const indexPattern = {} as IndexPattern; const items: SourceFiltersTableFilter[] = [{ value: 'tim*', clientId: '' }]; -const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IIndexPattern); +const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IndexPattern); const getTableColumnRender = ( component: ShallowWrapper, diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.tsx index f73d756f28116..c5b09961f25fc 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.tsx @@ -30,7 +30,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { IndexPattern } from 'src/plugins/data/public'; import { SourceFiltersTableFilter } from '../../types'; const filterHeader = i18n.translate( @@ -80,7 +80,7 @@ const cancelAria = i18n.translate( ); export interface TableProps { - indexPattern: IIndexPattern; + indexPattern: IndexPattern; items: SourceFiltersTableFilter[]; deleteFilter: Function; fieldWildcardMatcher: Function; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx index b00648f124716..cd311db513c09 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx @@ -30,7 +30,7 @@ export interface SourceFiltersTableProps { filterFilter: string; fieldWildcardMatcher: Function; onAddOrRemoveFilter?: Function; - saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject']; } export interface SourceFiltersTableState { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx index 101399ef02b73..5c29dfafd3c07 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx @@ -49,7 +49,7 @@ import { getTabs, getPath, convertToEuiSelectOption } from './utils'; interface TabsProps extends Pick { indexPattern: IndexPattern; fields: IndexPatternField[]; - saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject']; } const searchAriaLabel = i18n.translate( diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx index 2b484d1d837bf..4fae91e78f8f9 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx @@ -133,7 +133,7 @@ export interface FieldEdiorProps { spec: IndexPatternField['spec']; services: { redirectAway: () => void; - saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject']; }; } @@ -825,7 +825,7 @@ export class FieldEditor extends PureComponent { + .catch(() => { if (oldField) { indexPattern.fields.update(oldField); } else { diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.tsx index f049085ccff61..85b2c327e8b21 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor.tsx @@ -186,3 +186,7 @@ export class CodeEditor extends React.Component { } }; } + +// React.lazy requires default export +// eslint-disable-next-line import/no-default-export +export default CodeEditor; diff --git a/src/plugins/kibana_react/public/code_editor/index.tsx b/src/plugins/kibana_react/public/code_editor/index.tsx index 0ef83811d96d3..63e3d330a1c52 100644 --- a/src/plugins/kibana_react/public/code_editor/index.tsx +++ b/src/plugins/kibana_react/public/code_editor/index.tsx @@ -17,11 +17,23 @@ * under the License. */ import React from 'react'; +import { EuiDelayRender, EuiLoadingContent } from '@elastic/eui'; import { useUiSetting } from '../ui_settings'; -import { CodeEditor as BaseEditor, Props } from './code_editor'; +import type { Props } from './code_editor'; + +const LazyBaseEditor = React.lazy(() => import('./code_editor')); + +const Fallback = () => ( + + + +); export const CodeEditor: React.FunctionComponent = (props) => { const darkMode = useUiSetting('theme:darkMode'); - - return ; + return ( + }> + + + ); }; diff --git a/src/plugins/kibana_react/public/markdown/index.tsx b/src/plugins/kibana_react/public/markdown/index.tsx index cacf223cf33ed..4d6fd0b742ee4 100644 --- a/src/plugins/kibana_react/public/markdown/index.tsx +++ b/src/plugins/kibana_react/public/markdown/index.tsx @@ -17,5 +17,27 @@ * under the License. */ -export { MarkdownSimple } from './markdown_simple'; -export { Markdown } from './markdown'; +import React from 'react'; +import { EuiLoadingContent, EuiDelayRender } from '@elastic/eui'; +import type { MarkdownSimpleProps } from './markdown_simple'; +import type { MarkdownProps } from './markdown'; + +const Fallback = () => ( + + + +); + +const LazyMarkdownSimple = React.lazy(() => import('./markdown_simple')); +export const MarkdownSimple = (props: MarkdownSimpleProps) => ( + }> + + +); + +const LazyMarkdown = React.lazy(() => import('./markdown')); +export const Markdown = (props: MarkdownProps) => ( + }> + + +); diff --git a/src/plugins/kibana_react/public/markdown/markdown.tsx b/src/plugins/kibana_react/public/markdown/markdown.tsx index 15d1c4931e60b..8bb61bc71862e 100644 --- a/src/plugins/kibana_react/public/markdown/markdown.tsx +++ b/src/plugins/kibana_react/public/markdown/markdown.tsx @@ -84,7 +84,7 @@ export const markdownFactory = memoize( } ); -interface MarkdownProps extends React.HTMLAttributes { +export interface MarkdownProps extends React.HTMLAttributes { className?: string; markdown?: string; openLinksInNewTab?: boolean; @@ -112,3 +112,7 @@ export class Markdown extends PureComponent { ); } } + +// Needed for React.lazy +// eslint-disable-next-line import/no-default-export +export default Markdown; diff --git a/src/plugins/kibana_react/public/markdown/markdown_simple.tsx b/src/plugins/kibana_react/public/markdown/markdown_simple.tsx index a5465fd1c6fc9..71ae4e031abca 100644 --- a/src/plugins/kibana_react/public/markdown/markdown_simple.tsx +++ b/src/plugins/kibana_react/public/markdown/markdown_simple.tsx @@ -24,7 +24,7 @@ const markdownRenderers = { root: Fragment, }; -interface MarkdownSimpleProps { +export interface MarkdownSimpleProps { children: string; } @@ -32,3 +32,7 @@ interface MarkdownSimpleProps { export const MarkdownSimple = ({ children }: MarkdownSimpleProps) => ( {children} ); + +// Needed for React.lazy +// eslint-disable-next-line import/no-default-export +export default MarkdownSimple; diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index 2fa1debf51b5c..e0e295723a69d 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -556,3 +556,6 @@ class TableListView extends React.Component { const getUsageCollector = jest.fn(); const registerType = jest.fn(); const callCluster = jest.fn(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; beforeAll(() => registerApplicationUsageCollector(logger, usageCollectionMock, registerType, getUsageCollector) @@ -62,7 +67,7 @@ describe('telemetry_application_usage', () => { test('if no savedObjectClient initialised, return undefined', async () => { expect(collector.isReady()).toBe(false); - expect(await collector.fetch(callCluster)).toBeUndefined(); + expect(await collector.fetch(callCluster, esClient)).toBeUndefined(); jest.runTimersToTime(ROLL_INDICES_START); }); @@ -80,7 +85,7 @@ describe('telemetry_application_usage', () => { jest.runTimersToTime(ROLL_TOTAL_INDICES_INTERVAL); // Force rollTotals to run expect(collector.isReady()).toBe(true); - expect(await collector.fetch(callCluster)).toStrictEqual({}); + expect(await collector.fetch(callCluster, esClient)).toStrictEqual({}); expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled(); }); @@ -137,7 +142,7 @@ describe('telemetry_application_usage', () => { jest.runTimersToTime(ROLL_TOTAL_INDICES_INTERVAL); // Force rollTotals to run - expect(await collector.fetch(callCluster)).toStrictEqual({ + expect(await collector.fetch(callCluster, esClient)).toStrictEqual({ appId: { clicks_total: total + 1 + 10, clicks_7_days: total + 1, @@ -197,7 +202,7 @@ describe('telemetry_application_usage', () => { getUsageCollector.mockImplementation(() => savedObjectClient); - expect(await collector.fetch(callCluster)).toStrictEqual({ + expect(await collector.fetch(callCluster, esClient)).toStrictEqual({ appId: { clicks_total: 1, clicks_7_days: 0, diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts index 1adc0dc6896fd..e88d90fe5b24b 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts @@ -39,9 +39,12 @@ const TYPES = [ ]; export interface KibanaSavedObjectCounts { - [pluginName: string]: { - total: number; - }; + dashboard: { total: number }; + visualization: { total: number }; + search: { total: number }; + index_pattern: { total: number }; + graph_workspace: { total: number }; + timelion_sheet: { total: number }; } export async function getSavedObjectsCounts( @@ -71,7 +74,7 @@ export async function getSavedObjectsCounts( // Initialise the object with all zeros for all the types const allZeros: KibanaSavedObjectCounts = TYPES.reduce( (acc, type) => ({ ...acc, [snakeCase(type)]: { total: 0 } }), - {} + {} as KibanaSavedObjectCounts ); // Add the doc_count from each bucket diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts index 9cc079a9325d5..5b56e1a9b596f 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts @@ -22,15 +22,28 @@ import { take } from 'rxjs/operators'; import { SharedGlobalConfig } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { KIBANA_STATS_TYPE } from '../../../common/constants'; -import { getSavedObjectsCounts } from './get_saved_object_counts'; +import { getSavedObjectsCounts, KibanaSavedObjectCounts } from './get_saved_object_counts'; + +interface KibanaUsage extends KibanaSavedObjectCounts { + index: string; +} export function getKibanaUsageCollector( usageCollection: UsageCollectionSetup, legacyConfig$: Observable ) { - return usageCollection.makeUsageCollector({ + return usageCollection.makeUsageCollector({ type: 'kibana', isReady: () => true, + schema: { + index: { type: 'keyword' }, + dashboard: { total: { type: 'long' } }, + visualization: { total: { type: 'long' } }, + search: { total: { type: 'long' } }, + index_pattern: { total: { type: 'long' } }, + graph_workspace: { total: { type: 'long' } }, + timelion_sheet: { total: { type: 'long' } }, + }, async fetch(callCluster) { const { kibana: { index }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts new file mode 100644 index 0000000000000..792ac24b4de3d --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -0,0 +1,116 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MakeSchemaFrom } from 'src/plugins/usage_collection/server'; +import { UsageStats } from './telemetry_management_collector'; + +// Retrieved by changing all the current settings in Kibana (we'll need to revisit it in the future). +// I would suggest we use flattened type for the mappings of this collector. +export const stackManagementSchema: MakeSchemaFrom = { + 'visualize:enableLabs': { type: 'boolean' }, + 'visualization:heatmap:maxBuckets': { type: 'long' }, + 'visualization:colorMapping': { type: 'text' }, + 'visualization:regionmap:showWarnings': { type: 'boolean' }, + 'visualization:dimmingOpacity': { type: 'float' }, + 'visualization:tileMap:maxPrecision': { type: 'long' }, + 'securitySolution:ipReputationLinks': { type: 'text' }, + 'csv:separator': { type: 'keyword' }, + 'visualization:tileMap:WMSdefaults': { type: 'text' }, + 'timelion:target_buckets': { type: 'long' }, + 'timelion:max_buckets': { type: 'long' }, + 'timelion:es.timefield': { type: 'keyword' }, + 'timelion:min_interval': { type: 'keyword' }, + 'timelion:default_rows': { type: 'long' }, + 'timelion:default_columns': { type: 'long' }, + 'timelion:quandl.key': { type: 'keyword' }, + 'timelion:es.default_index': { type: 'keyword' }, + 'timelion:showTutorial': { type: 'boolean' }, + 'securitySolution:timeDefaults': { type: 'keyword' }, + 'securitySolution:defaultAnomalyScore': { type: 'long' }, + 'securitySolution:defaultIndex': { type: 'keyword' }, // it's an array + 'securitySolution:refreshIntervalDefaults': { type: 'keyword' }, + 'securitySolution:newsFeedUrl': { type: 'keyword' }, + 'securitySolution:enableNewsFeed': { type: 'boolean' }, + 'search:includeFrozen': { type: 'boolean' }, + 'courier:maxConcurrentShardRequests': { type: 'long' }, + 'courier:batchSearches': { type: 'boolean' }, + 'courier:setRequestPreference': { type: 'keyword' }, + 'courier:customRequestPreference': { type: 'keyword' }, + 'courier:ignoreFilterIfFieldNotInIndex': { type: 'boolean' }, + 'rollups:enableIndexPatterns': { type: 'boolean' }, + 'xpackReporting:customPdfLogo': { type: 'text' }, + 'notifications:lifetime:warning': { type: 'long' }, + 'notifications:lifetime:banner': { type: 'long' }, + 'notifications:lifetime:info': { type: 'long' }, + 'notifications:banner': { type: 'text' }, + 'notifications:lifetime:error': { type: 'long' }, + 'doc_table:highlight': { type: 'boolean' }, + 'discover:searchOnPageLoad': { type: 'boolean' }, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'doc_table:hideTimeColumn': { type: 'boolean' }, + 'discover:sampleSize': { type: 'long' }, + defaultColumns: { type: 'keyword' }, // it's an array + 'context:defaultSize': { type: 'long' }, + 'discover:aggs:terms:size': { type: 'long' }, + 'context:tieBreakerFields': { type: 'keyword' }, // it's an array + 'discover:sort:defaultOrder': { type: 'keyword' }, + 'context:step': { type: 'long' }, + 'accessibility:disableAnimations': { type: 'boolean' }, + 'ml:fileDataVisualizerMaxFileSize': { type: 'keyword' }, + 'ml:anomalyDetection:results:enableTimeDefaults': { type: 'boolean' }, + 'ml:anomalyDetection:results:timeDefaults': { type: 'keyword' }, + 'truncate:maxHeight': { type: 'long' }, + 'timepicker:timeDefaults': { type: 'keyword' }, + 'timepicker:refreshIntervalDefaults': { type: 'keyword' }, + 'timepicker:quickRanges': { type: 'keyword' }, + 'theme:version': { type: 'keyword' }, + 'theme:darkMode': { type: 'boolean' }, + 'state:storeInSessionStorage': { type: 'boolean' }, + 'savedObjects:perPage': { type: 'long' }, + 'search:queryLanguage': { type: 'keyword' }, + 'shortDots:enable': { type: 'boolean' }, + 'sort:options': { type: 'keyword' }, + 'savedObjects:listingLimit': { type: 'long' }, + 'query:queryString:options': { type: 'keyword' }, + pageNavigation: { type: 'keyword' }, + 'metrics:max_buckets': { type: 'long' }, + 'query:allowLeadingWildcards': { type: 'boolean' }, + metaFields: { type: 'keyword' }, // it's an array + 'indexPattern:placeholder': { type: 'keyword' }, + 'histogram:barTarget': { type: 'long' }, + 'histogram:maxBars': { type: 'long' }, + 'format:number:defaultLocale': { type: 'keyword' }, + 'format:percent:defaultPattern': { type: 'keyword' }, + 'format:number:defaultPattern': { type: 'keyword' }, + 'history:limit': { type: 'long' }, + 'format:defaultTypeMap': { type: 'keyword' }, + 'format:currency:defaultPattern': { type: 'keyword' }, + defaultIndex: { type: 'keyword' }, + 'format:bytes:defaultPattern': { type: 'keyword' }, + 'filters:pinnedByDefault': { type: 'boolean' }, + 'filterEditor:suggestValues': { type: 'boolean' }, + 'fields:popularLimit': { type: 'long' }, + dateNanosFormat: { type: 'keyword' }, + defaultRoute: { type: 'keyword' }, + 'dateFormat:tz': { type: 'keyword' }, + 'dateFormat:scaled': { type: 'keyword' }, + 'csv:quoteValues': { type: 'boolean' }, + 'dateFormat:dow': { type: 'keyword' }, + dateFormat: { type: 'keyword' }, +}; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts index 3a777beebd90a..612b1714020ef 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts @@ -19,8 +19,13 @@ import { IUiSettingsClient } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { stackManagementSchema } from './schema'; -export type UsageStats = Record; +export interface UsageStats extends Record { + // We don't support `type` yet. Only interfaces. So I added at least 1 known key to the generic + // Record extension to avoid eslint reverting it back to a `type` + 'visualize:enableLabs': boolean; +} export function createCollectorFetch(getUiSettingsClient: () => IUiSettingsClient | undefined) { return async function fetchUsageStats(): Promise { @@ -45,10 +50,11 @@ export function registerManagementUsageCollector( usageCollection: UsageCollectionSetup, getUiSettingsClient: () => IUiSettingsClient | undefined ) { - const collector = usageCollection.makeUsageCollector({ + const collector = usageCollection.makeUsageCollector({ type: 'stack_management', isReady: () => typeof getUiSettingsClient() !== 'undefined', fetch: createCollectorFetch(getUiSettingsClient), + schema: stackManagementSchema, }); usageCollection.registerCollector(collector); diff --git a/src/plugins/kibana_utils/public/history/redirect_when_missing.tsx b/src/plugins/kibana_utils/public/history/redirect_when_missing.tsx index d8121b656deeb..80e30d2b5dffe 100644 --- a/src/plugins/kibana_utils/public/history/redirect_when_missing.tsx +++ b/src/plugins/kibana_utils/public/history/redirect_when_missing.tsx @@ -20,12 +20,19 @@ import React, { Fragment } from 'react'; import { History } from 'history'; import { i18n } from '@kbn/i18n'; +import { EuiLoadingSpinner } from '@elastic/eui'; import ReactDOM from 'react-dom'; -import ReactMarkdown from 'react-markdown'; import { ApplicationStart, HttpStart, ToastsSetup } from 'kibana/public'; import { SavedObjectNotFound } from '..'; +const ReactMarkdown = React.lazy(() => import('react-markdown')); +const ErrorRenderer = (props: { children: string }) => ( + }> + + +); + interface Mapping { [key: string]: string | { app: string; path: string }; } @@ -96,16 +103,7 @@ export function redirectWhenMissing({ defaultMessage: 'Saved object is missing', }), text: (element: HTMLElement) => { - ReactDOM.render( - - {error.message} - , - element - ); + ReactDOM.render({error.message}, element); return () => ReactDOM.unmountComponentAtNode(element); }, }); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/index.js b/src/plugins/management/common/contants.ts similarity index 94% rename from src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/index.js rename to src/plugins/management/common/contants.ts index 8900db15321ae..6ff585510dab1 100644 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/index.js +++ b/src/plugins/management/common/contants.ts @@ -17,4 +17,4 @@ * under the License. */ -export default 'foo'; +export const MANAGEMENT_APP_ID = 'management'; diff --git a/src/plugins/management/public/index.ts b/src/plugins/management/public/index.ts index f6c23ccf0143f..f3e25b90b73c7 100644 --- a/src/plugins/management/public/index.ts +++ b/src/plugins/management/public/index.ts @@ -32,3 +32,5 @@ export { ManagementStart, DefinedSections, } from './types'; + +export { MANAGEMENT_APP_ID } from '../common/contants'; diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 808578c470ae1..122e73796753c 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -33,6 +33,7 @@ import { AppNavLinkStatus, } from '../../../core/public'; +import { MANAGEMENT_APP_ID } from '../common/contants'; import { ManagementSectionsService, getSectionsServiceStartPrivate, @@ -72,7 +73,7 @@ export class ManagementPlugin implements Plugin; -export const PLUGINS_DIR = resolve(__dirname, 'fixtures/plugins'); - -export function assertInvalidDirectoryError(error) { - if (!isInvalidDirectoryError(error)) { - throw new Error(`Expected ${inspect(error)} to be an 'InvalidDirectoryError'`); +export async function getServiceSettings(): Promise { + if (typeof loadPromise !== 'undefined') { + return loadPromise; } -} -export function assertInvalidPackError(error) { - if (!isInvalidPackError(error)) { - throw new Error(`Expected ${inspect(error)} to be an 'InvalidPackError'`); - } + loadPromise = new Promise(async (resolve) => { + const modules = await lazyLoadMapsLegacyModules(); + const config = getMapsLegacyConfig(); + // @ts-expect-error + resolve(new modules.ServiceSettings(config, config.tilemap)); + }); + return loadPromise; } diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts index 8f14cd1b15e2c..d31f23f4bc4a6 100644 --- a/src/plugins/maps_legacy/public/index.ts +++ b/src/plugins/maps_legacy/public/index.ts @@ -19,8 +19,6 @@ // @ts-ignore import { PluginInitializerContext } from 'kibana/public'; -// @ts-ignore -import { L } from './leaflet'; import { MapsLegacyPlugin } from './plugin'; // @ts-ignore import * as colorUtil from './map/color_util'; @@ -29,14 +27,14 @@ import { KibanaMapLayer } from './map/kibana_map_layer'; // @ts-ignore import { convertToGeoJson } from './map/convert_to_geojson'; // @ts-ignore -import { scaleBounds, getPrecision, geoContains } from './map/decode_geo_hash'; +import { getPrecision, geoContains } from './map/decode_geo_hash'; import { VectorLayer, FileLayerField, FileLayer, TmsLayer, IServiceSettings, -} from './map/service_settings'; +} from './map/service_settings_types'; // @ts-ignore import { mapTooltipProvider } from './tooltip_provider'; @@ -48,7 +46,6 @@ export function plugin(initializerContext: PluginInitializerContext) { /** @public */ export { - scaleBounds, getPrecision, geoContains, colorUtil, @@ -60,7 +57,6 @@ export { FileLayer, TmsLayer, mapTooltipProvider, - L, }; export * from './common/types'; @@ -68,5 +64,7 @@ export { ORIGIN } from './common/constants/origin'; export { WmsOptions } from './components/wms_options'; +export { lazyLoadMapsLegacyModules } from './lazy_load_bundle'; + export type MapsLegacyPluginSetup = ReturnType; export type MapsLegacyPluginStart = ReturnType; diff --git a/src/plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_info.js b/src/plugins/maps_legacy/public/lazy_load_bundle/index.ts similarity index 58% rename from src/plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_info.js rename to src/plugins/maps_legacy/public/lazy_load_bundle/index.ts index fe83b76cd1158..292949503a616 100644 --- a/src/plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_info.js +++ b/src/plugins/maps_legacy/public/lazy_load_bundle/index.ts @@ -17,23 +17,27 @@ * under the License. */ -import expect from '@kbn/expect'; -import sinon from 'sinon'; +let loadModulesPromise: Promise; -import { getClusterInfo } from '../get_cluster_info'; - -export function mockGetClusterInfo(callCluster, clusterInfo, req) { - callCluster.withArgs(req, 'info').returns(clusterInfo); - callCluster.withArgs('info').returns(clusterInfo); +interface LazyLoadedMapsLegacyModules { + KibanaMap: unknown; + L: unknown; + ServiceSettings: unknown; } -describe('get_cluster_info', () => { - it('uses callCluster to get info API', () => { - const callCluster = sinon.stub(); - const response = Promise.resolve({}); +export async function lazyLoadMapsLegacyModules(): Promise { + if (typeof loadModulesPromise !== 'undefined') { + return loadModulesPromise; + } - mockGetClusterInfo(callCluster, response); + loadModulesPromise = new Promise(async (resolve) => { + const { KibanaMap, L, ServiceSettings } = await import('./lazy'); - expect(getClusterInfo(callCluster)).to.be(response); + resolve({ + KibanaMap, + L, + ServiceSettings, + }); }); -}); + return loadModulesPromise; +} diff --git a/src/core/server/legacy/plugins/index.ts b/src/plugins/maps_legacy/public/lazy_load_bundle/lazy/index.ts similarity index 79% rename from src/core/server/legacy/plugins/index.ts rename to src/plugins/maps_legacy/public/lazy_load_bundle/lazy/index.ts index 7ec5dbc1983ab..5031b29e74b9a 100644 --- a/src/core/server/legacy/plugins/index.ts +++ b/src/plugins/maps_legacy/public/lazy_load_bundle/lazy/index.ts @@ -17,5 +17,9 @@ * under the License. */ -export { findLegacyPluginSpecs } from './find_legacy_plugin_specs'; -export { logLegacyThirdPartyPluginDeprecationWarning } from './log_legacy_plugins_warning'; +// @ts-expect-error +export { KibanaMap } from '../../map/kibana_map'; +// @ts-expect-error +export { ServiceSettings } from '../../map/service_settings'; +// @ts-expect-error +export { L } from '../../leaflet'; diff --git a/src/plugins/maps_legacy/public/map/base_maps_visualization.js b/src/plugins/maps_legacy/public/map/base_maps_visualization.js index 2d78fdc246e19..406dae43c9b5e 100644 --- a/src/plugins/maps_legacy/public/map/base_maps_visualization.js +++ b/src/plugins/maps_legacy/public/map/base_maps_visualization.js @@ -17,25 +17,22 @@ * under the License. */ -import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import * as Rx from 'rxjs'; import { filter, first } from 'rxjs/operators'; import { getEmsTileLayerId, getUiSettings, getToasts } from '../kibana_services'; +import { lazyLoadMapsLegacyModules } from '../lazy_load_bundle'; +import { getServiceSettings } from '../get_service_settings'; const WMS_MINZOOM = 0; const WMS_MAXZOOM = 22; //increase this to 22. Better for WMS -export function BaseMapsVisualizationProvider(getKibanaMap, mapServiceSettings) { +export function BaseMapsVisualizationProvider() { /** * Abstract base class for a visualization consisting of a map with a single baselayer. * @class BaseMapsVisualization * @constructor */ - - const serviceSettings = mapServiceSettings; - const toastService = getToasts(); - return class BaseMapsVisualization { constructor(element, vis) { this.vis = vis; @@ -95,9 +92,9 @@ export function BaseMapsVisualizationProvider(getKibanaMap, mapServiceSettings) const centerFromUIState = uiState.get('mapCenter'); options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : this.vis.params.mapZoom; options.center = centerFromUIState ? centerFromUIState : this.vis.params.mapCenter; - const services = { toastService }; - this._kibanaMap = getKibanaMap(this._container, options, services); + const modules = await lazyLoadMapsLegacyModules(); + this._kibanaMap = new modules.KibanaMap(this._container, options); this._kibanaMap.setMinZoom(WMS_MINZOOM); //use a default this._kibanaMap.setMaxZoom(WMS_MAXZOOM); //use a default @@ -138,6 +135,7 @@ export function BaseMapsVisualizationProvider(getKibanaMap, mapServiceSettings) const mapParams = this._getMapsParams(); if (!this._tmsConfigured()) { try { + const serviceSettings = await getServiceSettings(); const tmsServices = await serviceSettings.getTMSServices(); const userConfiguredTmsLayer = tmsServices[0]; const initBasemapLayer = userConfiguredTmsLayer @@ -147,7 +145,7 @@ export function BaseMapsVisualizationProvider(getKibanaMap, mapServiceSettings) this._setTmsLayer(initBasemapLayer); } } catch (e) { - toastService.addWarning(e.message); + getToasts().addWarning(e.message); return; } return; @@ -174,7 +172,7 @@ export function BaseMapsVisualizationProvider(getKibanaMap, mapServiceSettings) this._setTmsLayer(selectedTmsLayer); } } catch (tmsLoadingError) { - toastService.addWarning(tmsLoadingError.message); + getToasts().addWarning(tmsLoadingError.message); } } @@ -189,13 +187,14 @@ export function BaseMapsVisualizationProvider(getKibanaMap, mapServiceSettings) isDesaturated = true; } const isDarkMode = getUiSettings().get('theme:darkMode'); + const serviceSettings = await getServiceSettings(); const meta = await serviceSettings.getAttributesForTMSLayer( tmsLayer, isDesaturated, isDarkMode ); const showZoomMessage = serviceSettings.shouldShowZoomMessage(tmsLayer); - const options = _.cloneDeep(tmsLayer); + const options = { ...tmsLayer }; delete options.id; delete options.subdomains; this._kibanaMap.setBaseLayer({ @@ -228,12 +227,11 @@ export function BaseMapsVisualizationProvider(getKibanaMap, mapServiceSettings) } _getMapsParams() { - return _.assign( - {}, - this.vis.type.visConfig.defaults, - { type: this.vis.type.name }, - this._params - ); + return { + ...this.vis.type.visConfig.defaults, + type: this.vis.type.name, + ...this._params, + }; } _whenBaseLayerIsLoaded() { diff --git a/src/plugins/maps_legacy/public/map/decode_geo_hash.ts b/src/plugins/maps_legacy/public/map/decode_geo_hash.ts index 8c39ada03a46b..65184a8244777 100644 --- a/src/plugins/maps_legacy/public/map/decode_geo_hash.ts +++ b/src/plugins/maps_legacy/public/map/decode_geo_hash.ts @@ -17,8 +17,6 @@ * under the License. */ -import _ from 'lodash'; - interface DecodedGeoHash { latitude: number[]; longitude: number[]; @@ -101,33 +99,6 @@ interface GeoBoundingBox { bottom_right: GeoBoundingBoxCoordinate; } -export function scaleBounds(bounds: GeoBoundingBox): GeoBoundingBox { - const scale = 0.5; // scale bounds by 50% - - const topLeft = bounds.top_left; - const bottomRight = bounds.bottom_right; - let latDiff = _.round(Math.abs(topLeft.lat - bottomRight.lat), 5); - const lonDiff = _.round(Math.abs(bottomRight.lon - topLeft.lon), 5); - // map height can be zero when vis is first created - if (latDiff === 0) latDiff = lonDiff; - - const latDelta = latDiff * scale; - let topLeftLat = _.round(topLeft.lat, 5) + latDelta; - if (topLeftLat > 90) topLeftLat = 90; - let bottomRightLat = _.round(bottomRight.lat, 5) - latDelta; - if (bottomRightLat < -90) bottomRightLat = -90; - const lonDelta = lonDiff * scale; - let topLeftLon = _.round(topLeft.lon, 5) - lonDelta; - if (topLeftLon < -180) topLeftLon = -180; - let bottomRightLon = _.round(bottomRight.lon, 5) + lonDelta; - if (bottomRightLon > 180) bottomRightLon = 180; - - return { - top_left: { lat: topLeftLat, lon: topLeftLon }, - bottom_right: { lat: bottomRightLat, lon: bottomRightLon }, - }; -} - export function geoContains(collar?: GeoBoundingBox, bounds?: GeoBoundingBox) { if (!bounds || !collar) return false; // test if bounds top_left is outside collar diff --git a/src/plugins/maps_legacy/public/map/grid_dimensions.js b/src/plugins/maps_legacy/public/map/grid_dimensions.js index d146adf2ca67f..0f84e972104ba 100644 --- a/src/plugins/maps_legacy/public/map/grid_dimensions.js +++ b/src/plugins/maps_legacy/public/map/grid_dimensions.js @@ -17,8 +17,6 @@ * under the License. */ -import _ from 'lodash'; - // geohash precision mapping of geohash grid cell dimensions (width x height, in meters) at equator. // https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator const gridAtEquator = { @@ -37,5 +35,5 @@ const gridAtEquator = { }; export function gridDimensions(precision) { - return _.get(gridAtEquator, precision); + return gridAtEquator[precision]; } diff --git a/src/plugins/maps_legacy/public/map/kibana_map.js b/src/plugins/maps_legacy/public/map/kibana_map.js index ad5d2c089b875..3948692e55676 100644 --- a/src/plugins/maps_legacy/public/map/kibana_map.js +++ b/src/plugins/maps_legacy/public/map/kibana_map.js @@ -20,7 +20,7 @@ import { EventEmitter } from 'events'; import { createZoomWarningMsg } from './map_messages'; import $ from 'jquery'; -import _ from 'lodash'; +import { get, isEqual, escape } from 'lodash'; import { zoomToPrecision } from './zoom_to_precision'; import { i18n } from '@kbn/i18n'; import { ORIGIN } from '../common/constants/origin'; @@ -380,7 +380,7 @@ export class KibanaMap extends EventEmitter { const distanceX = latLngC.distanceTo(latLngX); // calculate distance between c and x (latitude) const distanceY = latLngC.distanceTo(latLngY); // calculate distance between c and y (longitude) - return _.min([distanceX, distanceY]); + return Math.min(distanceX, distanceY); } _getLeafletBounds(resizeOnFail) { @@ -544,7 +544,7 @@ export class KibanaMap extends EventEmitter { } setBaseLayer(settings) { - if (_.isEqual(settings, this._baseLayerSettings)) { + if (isEqual(settings, this._baseLayerSettings)) { return; } @@ -567,7 +567,7 @@ export class KibanaMap extends EventEmitter { let baseLayer; if (settings.baseLayerType === 'wms') { //This is user-input that is rendered with the Leaflet attribution control. Needs to be sanitized. - this._baseLayerSettings.options.attribution = _.escape(settings.options.attribution); + this._baseLayerSettings.options.attribution = escape(settings.options.attribution); baseLayer = this._getWMSBaseLayer(settings.options); } else if (settings.baseLayerType === 'tms') { baseLayer = this._getTMSBaseLayer(settings.options); @@ -661,7 +661,7 @@ export class KibanaMap extends EventEmitter { _updateDesaturation() { const tiles = $('img.leaflet-tile-loaded'); // Don't apply client-side styling to EMS basemaps - if (_.get(this._baseLayerSettings, 'options.origin') === ORIGIN.EMS) { + if (get(this._baseLayerSettings, 'options.origin') === ORIGIN.EMS) { tiles.addClass('filters-off'); } else { if (this._baseLayerIsDesaturated) { diff --git a/src/plugins/maps_legacy/public/map/service_settings.d.ts b/src/plugins/maps_legacy/public/map/service_settings_types.ts similarity index 100% rename from src/plugins/maps_legacy/public/map/service_settings.d.ts rename to src/plugins/maps_legacy/public/map/service_settings_types.ts diff --git a/src/plugins/maps_legacy/public/plugin.ts b/src/plugins/maps_legacy/public/plugin.ts index 8c9f1e9cef194..17cee226cb70c 100644 --- a/src/plugins/maps_legacy/public/plugin.ts +++ b/src/plugins/maps_legacy/public/plugin.ts @@ -22,15 +22,12 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/p // @ts-ignore import { setToasts, setUiSettings, setKibanaVersion, setMapsLegacyConfig } from './kibana_services'; // @ts-ignore -import { ServiceSettings } from './map/service_settings'; -// @ts-ignore import { getPrecision, getZoomPrecision } from './map/precision'; -// @ts-ignore -import { KibanaMap } from './map/kibana_map'; import { MapsLegacyPluginSetup, MapsLegacyPluginStart } from './index'; import { MapsLegacyConfig } from '../config'; // @ts-ignore import { BaseMapsVisualizationProvider } from './map/base_maps_visualization'; +import { getServiceSettings } from './get_service_settings'; /** * These are the interfaces with your public contracts. You should export these @@ -67,17 +64,13 @@ export class MapsLegacyPlugin implements Plugin new KibanaMap(...args); - const getBaseMapsVis = () => - new BaseMapsVisualizationProvider(getKibanaMapFactoryProvider, serviceSettings); + const getBaseMapsVis = () => new BaseMapsVisualizationProvider(); return { - serviceSettings, + getServiceSettings, getZoomPrecision, getPrecision, config, - getKibanaMapFactoryProvider, getBaseMapsVis, }; } diff --git a/src/plugins/region_map/public/choropleth_layer.js b/src/plugins/region_map/public/choropleth_layer.js index 30fa8b544cdec..14a91e189e0d5 100644 --- a/src/plugins/region_map/public/choropleth_layer.js +++ b/src/plugins/region_map/public/choropleth_layer.js @@ -33,7 +33,7 @@ const EMPTY_STYLE = { fillOpacity: 0, }; -export default class ChoroplethLayer extends KibanaMapLayer { +export class ChoroplethLayer extends KibanaMapLayer { static _doInnerJoin(sortedMetrics, sortedGeojsonFeatures, joinField) { let j = 0; for (let i = 0; i < sortedGeojsonFeatures.length; i++) { @@ -71,7 +71,16 @@ export default class ChoroplethLayer extends KibanaMapLayer { } } - constructor(name, attribution, format, showAllShapes, meta, layerConfig, serviceSettings) { + constructor( + name, + attribution, + format, + showAllShapes, + meta, + layerConfig, + serviceSettings, + leaflet + ) { super(); this._serviceSettings = serviceSettings; this._metrics = null; @@ -84,9 +93,10 @@ export default class ChoroplethLayer extends KibanaMapLayer { this._showAllShapes = showAllShapes; this._layerName = name; this._layerConfig = layerConfig; + this._leaflet = leaflet; // eslint-disable-next-line no-undef - this._leafletLayer = L.geoJson(null, { + this._leafletLayer = this._leaflet.geoJson(null, { onEachFeature: (feature, layer) => { layer.on('click', () => { this.emit('select', feature.properties[this._joinField]); @@ -97,7 +107,7 @@ export default class ChoroplethLayer extends KibanaMapLayer { const tooltipContents = this._tooltipFormatter(feature); if (!location) { // eslint-disable-next-line no-undef - const leafletGeojson = L.geoJson(feature); + const leafletGeojson = this._leaflet.geoJson(feature); location = leafletGeojson.getBounds().getCenter(); } this.emit('showTooltip', { @@ -425,7 +435,7 @@ CORS configuration of the server permits requests from the Kibana application on const { min, max } = getMinMax(this._metrics); // eslint-disable-next-line no-undef - const boundsOfAllFeatures = new L.LatLngBounds(); + const boundsOfAllFeatures = new this._leaflet.LatLngBounds(); return { leafletStyleFunction: (geojsonFeature) => { const match = geojsonFeature.__kbnJoinedMetric; @@ -433,7 +443,7 @@ CORS configuration of the server permits requests from the Kibana application on return emptyStyle(); } // eslint-disable-next-line no-undef - const boundsOfFeature = L.geoJson(geojsonFeature).getBounds(); + const boundsOfFeature = this._leaflet.geoJson(geojsonFeature).getBounds(); boundsOfAllFeatures.extend(boundsOfFeature); return { diff --git a/src/plugins/region_map/public/components/region_map_options.tsx b/src/plugins/region_map/public/components/region_map_options.tsx index be3d7fe86ab3f..4d564d7347a1e 100644 --- a/src/plugins/region_map/public/components/region_map_options.tsx +++ b/src/plugins/region_map/public/components/region_map_options.tsx @@ -37,11 +37,11 @@ const mapFieldForOption = ({ description, name }: FileLayerField) => ({ }); export type RegionMapOptionsProps = { - serviceSettings: IServiceSettings; + getServiceSettings: () => Promise; } & VisOptionsProps; function RegionMapOptions(props: RegionMapOptionsProps) { - const { serviceSettings, stateParams, vis, setValue } = props; + const { getServiceSettings, stateParams, vis, setValue } = props; const { vectorLayers } = vis.type.editorConfig.collections; const vectorLayerOptions = useMemo(() => vectorLayers.map(mapLayerForOption), [vectorLayers]); const fieldOptions = useMemo( @@ -54,10 +54,11 @@ function RegionMapOptions(props: RegionMapOptionsProps) { const setEmsHotLink = useCallback( async (layer: VectorLayer) => { + const serviceSettings = await getServiceSettings(); const emsHotLink = await serviceSettings.getEMSHotLink(layer); setValue('emsHotLink', emsHotLink); }, - [setValue, serviceSettings] + [setValue, getServiceSettings] ); const setLayer = useCallback( diff --git a/src/plugins/region_map/public/plugin.ts b/src/plugins/region_map/public/plugin.ts index ec9ee94310578..c641c16a8112b 100644 --- a/src/plugins/region_map/public/plugin.ts +++ b/src/plugins/region_map/public/plugin.ts @@ -41,7 +41,7 @@ import { KibanaLegacyStart } from '../../kibana_legacy/public'; interface RegionMapVisualizationDependencies { uiSettings: IUiSettingsClient; regionmapsConfig: RegionMapsConfig; - serviceSettings: IServiceSettings; + getServiceSettings: () => Promise; BaseMapsVisualization: any; } @@ -93,7 +93,7 @@ export class RegionMapPlugin implements Plugin = { uiSettings: core.uiSettings, regionmapsConfig: config as RegionMapsConfig, - serviceSettings: mapsLegacy.serviceSettings, + getServiceSettings: mapsLegacy.getServiceSettings, BaseMapsVisualization: mapsLegacy.getBaseMapsVis(), }; diff --git a/src/plugins/region_map/public/region_map_type.js b/src/plugins/region_map/public/region_map_type.js index def95950e6151..036726f6a4a9e 100644 --- a/src/plugins/region_map/public/region_map_type.js +++ b/src/plugins/region_map/public/region_map_type.js @@ -26,7 +26,7 @@ import { Schemas } from '../../vis_default_editor/public'; import { ORIGIN } from '../../maps_legacy/public'; export function createRegionMapTypeDefinition(dependencies) { - const { uiSettings, regionmapsConfig, serviceSettings } = dependencies; + const { uiSettings, regionmapsConfig, getServiceSettings } = dependencies; const visualization = createRegionMapVisualization(dependencies); return { @@ -54,7 +54,9 @@ provided base maps, or add your own. Darker colors represent higher values.', }, visualization, editorConfig: { - optionsTemplate: (props) => , + optionsTemplate: (props) => ( + + ), collections: { colorSchemas: truncatedColorSchemas, vectorLayers: [], @@ -97,6 +99,7 @@ provided base maps, or add your own. Darker colors represent higher values.', ]), }, setup: async (vis) => { + const serviceSettings = await getServiceSettings(); const tmsLayers = await serviceSettings.getTMSServices(); vis.type.editorConfig.collections.tmsLayers = tmsLayers; if (!vis.params.wms.selectedTmsLayer && tmsLayers.length) { diff --git a/src/plugins/region_map/public/region_map_visualization.js b/src/plugins/region_map/public/region_map_visualization.js index 43959c367558f..9b20a35630c86 100644 --- a/src/plugins/region_map/public/region_map_visualization.js +++ b/src/plugins/region_map/public/region_map_visualization.js @@ -18,18 +18,16 @@ */ import { i18n } from '@kbn/i18n'; -import ChoroplethLayer from './choropleth_layer'; import { getFormatService, getNotifications, getKibanaLegacy } from './kibana_services'; import { truncatedColorMaps } from '../../charts/public'; import { tooltipFormatter } from './tooltip_formatter'; -import { mapTooltipProvider, ORIGIN } from '../../maps_legacy/public'; -import _ from 'lodash'; +import { mapTooltipProvider, ORIGIN, lazyLoadMapsLegacyModules } from '../../maps_legacy/public'; export function createRegionMapVisualization({ regionmapsConfig, - serviceSettings, uiSettings, BaseMapsVisualization, + getServiceSettings, }) { return class RegionMapsVisualization extends BaseMapsVisualization { constructor(container, vis) { @@ -71,7 +69,7 @@ export function createRegionMapVisualization({ return; } - this._updateChoroplethLayerForNewMetrics( + await this._updateChoroplethLayerForNewMetrics( selectedLayer.name, selectedLayer.attribution, this._params.showAllShapes, @@ -98,10 +96,13 @@ export function createRegionMapVisualization({ // Do not use the selectedLayer from the visState. // These settings are stored in the URL and can be used to inject dirty display content. + const { escape } = await import('lodash'); + if ( fileLayerConfig.isEMS || //Hosted by EMS. Metadata needs to be resolved through EMS (fileLayerConfig.layerId && fileLayerConfig.layerId.startsWith(`${ORIGIN.EMS}.`)) //fallback for older saved objects ) { + const serviceSettings = await getServiceSettings(); return await serviceSettings.loadFileLayerConfig(fileLayerConfig); } @@ -113,7 +114,7 @@ export function createRegionMapVisualization({ if (configuredLayer) { return { ...configuredLayer, - attribution: _.escape(configuredLayer.attribution ? configuredLayer.attribution : ''), + attribution: escape(configuredLayer.attribution ? configuredLayer.attribution : ''), }; } @@ -133,7 +134,7 @@ export function createRegionMapVisualization({ return; } - this._updateChoroplethLayerForNewProperties( + await this._updateChoroplethLayerForNewProperties( selectedLayer.name, selectedLayer.attribution, this._params.showAllShapes @@ -151,24 +152,24 @@ export function createRegionMapVisualization({ ); } - _updateChoroplethLayerForNewMetrics(name, attribution, showAllData, newMetrics) { + async _updateChoroplethLayerForNewMetrics(name, attribution, showAllData, newMetrics) { if ( this._choroplethLayer && this._choroplethLayer.canReuseInstanceForNewMetrics(name, showAllData, newMetrics) ) { return; } - return this._recreateChoroplethLayer(name, attribution, showAllData); + await this._recreateChoroplethLayer(name, attribution, showAllData); } - _updateChoroplethLayerForNewProperties(name, attribution, showAllData) { + async _updateChoroplethLayerForNewProperties(name, attribution, showAllData) { if (this._choroplethLayer && this._choroplethLayer.canReuseInstance(name, showAllData)) { return; } - return this._recreateChoroplethLayer(name, attribution, showAllData); + await this._recreateChoroplethLayer(name, attribution, showAllData); } - _recreateChoroplethLayer(name, attribution, showAllData) { + async _recreateChoroplethLayer(name, attribution, showAllData) { this._kibanaMap.removeLayer(this._choroplethLayer); if (this._choroplethLayer) { @@ -179,9 +180,10 @@ export function createRegionMapVisualization({ showAllData, this._params.selectedLayer.meta, this._params.selectedLayer, - serviceSettings + await getServiceSettings() ); } else { + const { ChoroplethLayer } = await import('./choropleth_layer'); this._choroplethLayer = new ChoroplethLayer( name, attribution, @@ -189,7 +191,8 @@ export function createRegionMapVisualization({ showAllData, this._params.selectedLayer.meta, this._params.selectedLayer, - serviceSettings + await getServiceSettings(), + (await lazyLoadMapsLegacyModules()).L ); } diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx index ce08151d37c2c..dfc0c4049774d 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx @@ -33,6 +33,8 @@ interface SaveModalDocumentInfo { interface OriginSaveModalProps { originatingApp?: string; getAppNameFromId?: (appId: string) => string | undefined; + originatingAppName?: string; + returnToOriginSwitchLabel?: string; documentInfo: SaveModalDocumentInfo; objectType: string; onClose: () => void; @@ -73,11 +75,13 @@ export function SavedObjectSaveModalOrigin(props: OriginSaveModalProps) { setReturnToOriginMode(event.target.checked); }} label={ - + props.returnToOriginSwitchLabel ?? ( + + ) } /> diff --git a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts index 679ea5ffc23ee..eb95c213e680d 100644 --- a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts +++ b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts @@ -24,10 +24,11 @@ import { SavedObject, SavedObjectLoader } from '../../../saved_objects/public'; import { DataPublicPluginStart, IndexPatternsContract, - IIndexPattern, injectSearchSourceReferences, + IndexPatternSpec, } from '../../../data/public'; import { FailedImport } from './process_import_response'; +import { DuplicateIndexPatternError, IndexPattern } from '../../../data/public'; type SavedObjectsRawDoc = Record; @@ -70,11 +71,10 @@ function addJsonFieldToIndexPattern( async function importIndexPattern( doc: SavedObjectsRawDoc, indexPatterns: IndexPatternsContract, - overwriteAll: boolean, + overwriteAll: boolean = false, openConfirm: OverlayStart['openConfirm'] ) { // TODO: consolidate this is the code in create_index_pattern_wizard.js - const emptyPattern = await indexPatterns.make(); const { title, timeFieldName, @@ -84,50 +84,53 @@ async function importIndexPattern( type, typeMeta, } = doc._source; - const importedIndexPattern = { + const indexPatternSpec: IndexPatternSpec = { id: doc._id, title, timeFieldName, - } as IIndexPattern; + }; + let emptyPattern: IndexPattern; if (type) { - importedIndexPattern.type = type; + indexPatternSpec.type = type; } - addJsonFieldToIndexPattern(importedIndexPattern, fields, 'fields', title); - addJsonFieldToIndexPattern(importedIndexPattern, fieldFormatMap, 'fieldFormatMap', title); - addJsonFieldToIndexPattern(importedIndexPattern, sourceFilters, 'sourceFilters', title); - addJsonFieldToIndexPattern(importedIndexPattern, typeMeta, 'typeMeta', title); - Object.assign(emptyPattern, importedIndexPattern); - - let newId = await emptyPattern.create(overwriteAll); - if (!newId) { - // We can override and we want to prompt for confirmation - const isConfirmed = await openConfirm( - i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteLabel', { - values: { title }, - defaultMessage: "Are you sure you want to overwrite '{title}'?", - }), - { - title: i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteTitle', { - defaultMessage: 'Overwrite {type}?', - values: { type }, + addJsonFieldToIndexPattern(indexPatternSpec, fields, 'fields', title); + addJsonFieldToIndexPattern(indexPatternSpec, fieldFormatMap, 'fieldFormatMap', title); + addJsonFieldToIndexPattern(indexPatternSpec, sourceFilters, 'sourceFilters', title); + addJsonFieldToIndexPattern(indexPatternSpec, typeMeta, 'typeMeta', title); + try { + emptyPattern = await indexPatterns.createAndSave(indexPatternSpec, overwriteAll, true); + } catch (err) { + if (err instanceof DuplicateIndexPatternError) { + // We can override and we want to prompt for confirmation + const isConfirmed = await openConfirm( + i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteLabel', { + values: { title }, + defaultMessage: "Are you sure you want to overwrite '{title}'?", }), - confirmButtonText: i18n.translate( - 'savedObjectsManagement.indexPattern.confirmOverwriteButton', - { - defaultMessage: 'Overwrite', - } - ), - } - ); + { + title: i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteTitle', { + defaultMessage: 'Overwrite {type}?', + values: { type }, + }), + confirmButtonText: i18n.translate( + 'savedObjectsManagement.indexPattern.confirmOverwriteButton', + { + defaultMessage: 'Overwrite', + } + ), + } + ); - if (isConfirmed) { - newId = (await emptyPattern.create(true)) as string; - } else { - return; + if (isConfirmed) { + emptyPattern = await indexPatterns.createAndSave(indexPatternSpec, true, true); + } else { + return; + } } } - indexPatterns.clearCache(newId); - return newId; + + indexPatterns.clearCache(emptyPattern!.id); + return emptyPattern!.id; } async function importDocument(obj: SavedObject, doc: SavedObjectsRawDoc, overwriteAll: boolean) { diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 5bce03a292760..3ee0c181203aa 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -1297,6 +1297,326 @@ } } }, + "kibana": { + "properties": { + "index": { + "type": "keyword" + }, + "dashboard": { + "properties": { + "total": { + "type": "long" + } + } + }, + "visualization": { + "properties": { + "total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "total": { + "type": "long" + } + } + }, + "index_pattern": { + "properties": { + "total": { + "type": "long" + } + } + }, + "graph_workspace": { + "properties": { + "total": { + "type": "long" + } + } + }, + "timelion_sheet": { + "properties": { + "total": { + "type": "long" + } + } + } + } + }, + "stack_management": { + "properties": { + "visualize:enableLabs": { + "type": "boolean" + }, + "visualization:heatmap:maxBuckets": { + "type": "long" + }, + "visualization:colorMapping": { + "type": "text" + }, + "visualization:regionmap:showWarnings": { + "type": "boolean" + }, + "visualization:dimmingOpacity": { + "type": "float" + }, + "visualization:tileMap:maxPrecision": { + "type": "long" + }, + "securitySolution:ipReputationLinks": { + "type": "text" + }, + "csv:separator": { + "type": "keyword" + }, + "visualization:tileMap:WMSdefaults": { + "type": "text" + }, + "timelion:target_buckets": { + "type": "long" + }, + "timelion:max_buckets": { + "type": "long" + }, + "timelion:es.timefield": { + "type": "keyword" + }, + "timelion:min_interval": { + "type": "keyword" + }, + "timelion:default_rows": { + "type": "long" + }, + "timelion:default_columns": { + "type": "long" + }, + "timelion:quandl.key": { + "type": "keyword" + }, + "timelion:es.default_index": { + "type": "keyword" + }, + "timelion:showTutorial": { + "type": "boolean" + }, + "securitySolution:timeDefaults": { + "type": "keyword" + }, + "securitySolution:defaultAnomalyScore": { + "type": "long" + }, + "securitySolution:defaultIndex": { + "type": "keyword" + }, + "securitySolution:refreshIntervalDefaults": { + "type": "keyword" + }, + "securitySolution:newsFeedUrl": { + "type": "keyword" + }, + "securitySolution:enableNewsFeed": { + "type": "boolean" + }, + "search:includeFrozen": { + "type": "boolean" + }, + "courier:maxConcurrentShardRequests": { + "type": "long" + }, + "courier:batchSearches": { + "type": "boolean" + }, + "courier:setRequestPreference": { + "type": "keyword" + }, + "courier:customRequestPreference": { + "type": "keyword" + }, + "courier:ignoreFilterIfFieldNotInIndex": { + "type": "boolean" + }, + "rollups:enableIndexPatterns": { + "type": "boolean" + }, + "xpackReporting:customPdfLogo": { + "type": "text" + }, + "notifications:lifetime:warning": { + "type": "long" + }, + "notifications:lifetime:banner": { + "type": "long" + }, + "notifications:lifetime:info": { + "type": "long" + }, + "notifications:banner": { + "type": "text" + }, + "notifications:lifetime:error": { + "type": "long" + }, + "doc_table:highlight": { + "type": "boolean" + }, + "discover:searchOnPageLoad": { + "type": "boolean" + }, + "doc_table:hideTimeColumn": { + "type": "boolean" + }, + "discover:sampleSize": { + "type": "long" + }, + "defaultColumns": { + "type": "keyword" + }, + "context:defaultSize": { + "type": "long" + }, + "discover:aggs:terms:size": { + "type": "long" + }, + "context:tieBreakerFields": { + "type": "keyword" + }, + "discover:sort:defaultOrder": { + "type": "keyword" + }, + "context:step": { + "type": "long" + }, + "accessibility:disableAnimations": { + "type": "boolean" + }, + "ml:fileDataVisualizerMaxFileSize": { + "type": "keyword" + }, + "ml:anomalyDetection:results:enableTimeDefaults": { + "type": "boolean" + }, + "ml:anomalyDetection:results:timeDefaults": { + "type": "keyword" + }, + "truncate:maxHeight": { + "type": "long" + }, + "timepicker:timeDefaults": { + "type": "keyword" + }, + "timepicker:refreshIntervalDefaults": { + "type": "keyword" + }, + "timepicker:quickRanges": { + "type": "keyword" + }, + "theme:version": { + "type": "keyword" + }, + "theme:darkMode": { + "type": "boolean" + }, + "state:storeInSessionStorage": { + "type": "boolean" + }, + "savedObjects:perPage": { + "type": "long" + }, + "search:queryLanguage": { + "type": "keyword" + }, + "shortDots:enable": { + "type": "boolean" + }, + "sort:options": { + "type": "keyword" + }, + "savedObjects:listingLimit": { + "type": "long" + }, + "query:queryString:options": { + "type": "keyword" + }, + "pageNavigation": { + "type": "keyword" + }, + "metrics:max_buckets": { + "type": "long" + }, + "query:allowLeadingWildcards": { + "type": "boolean" + }, + "metaFields": { + "type": "keyword" + }, + "indexPattern:placeholder": { + "type": "keyword" + }, + "histogram:barTarget": { + "type": "long" + }, + "histogram:maxBars": { + "type": "long" + }, + "format:number:defaultLocale": { + "type": "keyword" + }, + "format:percent:defaultPattern": { + "type": "keyword" + }, + "format:number:defaultPattern": { + "type": "keyword" + }, + "history:limit": { + "type": "long" + }, + "format:defaultTypeMap": { + "type": "keyword" + }, + "format:currency:defaultPattern": { + "type": "keyword" + }, + "defaultIndex": { + "type": "keyword" + }, + "format:bytes:defaultPattern": { + "type": "keyword" + }, + "filters:pinnedByDefault": { + "type": "boolean" + }, + "filterEditor:suggestValues": { + "type": "boolean" + }, + "fields:popularLimit": { + "type": "long" + }, + "dateNanosFormat": { + "type": "keyword" + }, + "defaultRoute": { + "type": "keyword" + }, + "dateFormat:tz": { + "type": "keyword" + }, + "dateFormat:scaled": { + "type": "keyword" + }, + "csv:quoteValues": { + "type": "boolean" + }, + "dateFormat:dow": { + "type": "keyword" + }, + "dateFormat": { + "type": "keyword" + } + } + }, "telemetry": { "properties": { "opt_in_status": { @@ -1310,6 +1630,121 @@ } } }, + "static_telemetry": { + "properties": { + "ece": { + "properties": { + "kb_uuid": { + "type": "keyword" + }, + "es_uuid": { + "type": "keyword" + }, + "account_id": { + "type": "keyword" + }, + "license": { + "properties": { + "uuid": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "issued_to": { + "type": "text" + }, + "issuer": { + "type": "text" + }, + "issue_date_in_millis": { + "type": "long" + }, + "start_date_in_millis": { + "type": "long" + }, + "expiry_date_in_millis": { + "type": "long" + }, + "max_resource_units": { + "type": "long" + } + } + } + } + }, + "ess": { + "properties": { + "kb_uuid": { + "type": "keyword" + }, + "es_uuid": { + "type": "keyword" + }, + "account_id": { + "type": "keyword" + }, + "license": { + "properties": { + "uuid": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "issued_to": { + "type": "text" + }, + "issuer": { + "type": "text" + }, + "issue_date_in_millis": { + "type": "long" + }, + "start_date_in_millis": { + "type": "long" + }, + "expiry_date_in_millis": { + "type": "long" + }, + "max_resource_units": { + "type": "long" + } + } + } + } + }, + "eck": { + "properties": { + "operator_uuid": { + "type": "keyword" + }, + "operator_roles": { + "type": "keyword" + }, + "custom_operator_namespace": { + "type": "boolean" + }, + "distribution": { + "type": "text" + }, + "build": { + "properties": { + "hash": { + "type": "text" + }, + "date": { + "type": "date" + }, + "version": { + "type": "keyword" + } + } + } + } + } + } + }, "tsvb-validation": { "properties": { "failed_validations": { diff --git a/src/plugins/telemetry/server/collectors/usage/schema.ts b/src/plugins/telemetry/server/collectors/usage/schema.ts new file mode 100644 index 0000000000000..8f4d555d75c49 --- /dev/null +++ b/src/plugins/telemetry/server/collectors/usage/schema.ts @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MakeSchemaFrom } from 'src/plugins/usage_collection/server'; +import { LicenseUsage, StaticTelemetryUsage } from './telemetry_usage_collector'; + +const licenseSchema: MakeSchemaFrom = { + uuid: { type: 'keyword' }, + type: { type: 'keyword' }, + issued_to: { type: 'text' }, + issuer: { type: 'text' }, + issue_date_in_millis: { type: 'long' }, + start_date_in_millis: { type: 'long' }, + expiry_date_in_millis: { type: 'long' }, + max_resource_units: { type: 'long' }, +}; + +export const staticTelemetrySchema: MakeSchemaFrom> = { + ece: { + kb_uuid: { type: 'keyword' }, + es_uuid: { type: 'keyword' }, + account_id: { type: 'keyword' }, + license: licenseSchema, + }, + ess: { + kb_uuid: { type: 'keyword' }, + es_uuid: { type: 'keyword' }, + account_id: { type: 'keyword' }, + license: licenseSchema, + }, + eck: { + operator_uuid: { type: 'keyword' }, + operator_roles: { type: 'keyword' }, + custom_operator_namespace: { type: 'boolean' }, + distribution: { type: 'text' }, + build: { + hash: { type: 'text' }, + date: { type: 'date' }, + version: { type: 'keyword' }, + }, + }, +}; diff --git a/src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts b/src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts index bde7cfa5c4445..39f8ef0151a0b 100644 --- a/src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts +++ b/src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts @@ -29,6 +29,7 @@ import { TelemetryConfigType } from '../../config'; // look for telemetry.yml in the same places we expect kibana.yml import { ensureDeepObject } from './ensure_deep_object'; +import { staticTelemetrySchema } from './schema'; /** * The maximum file size before we ignore it (note: this limit is arbitrary). @@ -60,10 +61,12 @@ export function isFileReadable(path: string): boolean { * @param configPath The config file path. * @returns The unmodified JSON object if the file exists and is a valid YAML file. */ -export async function readTelemetryFile(path: string): Promise { +export async function readTelemetryFile( + configPath: string +): Promise { try { - if (isFileReadable(path)) { - const yaml = readFileSync(path); + if (isFileReadable(configPath)) { + const yaml = readFileSync(configPath); const data = safeLoad(yaml.toString()); // don't bother returning empty objects @@ -79,11 +82,48 @@ export async function readTelemetryFile(path: string): Promise Promise ) { - return usageCollection.makeUsageCollector({ + return usageCollection.makeUsageCollector({ type: 'static_telemetry', isReady: () => true, fetch: async () => { @@ -91,6 +131,7 @@ export function createTelemetryUsageCollector( const telemetryPath = join(dirname(configPath), 'telemetry.yml'); return await readTelemetryFile(telemetryPath); }, + schema: staticTelemetrySchema, }); } diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index 005c5f96d98d0..dfbbe3355e69c 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -34,6 +34,7 @@ import { SavedObjectsClient, Plugin, Logger, + IClusterClient, } from '../../../core/server'; import { registerRoutes } from './routes'; import { registerCollection } from './telemetry_collection'; @@ -83,6 +84,7 @@ export class TelemetryPlugin implements Plugin) { this.logger = initializerContext.logger.get(); @@ -102,8 +104,11 @@ export class TelemetryPlugin implements Plugin this.elasticsearchClient + ); const router = http.createRouter(); registerRoutes({ @@ -126,14 +131,12 @@ export class TelemetryPlugin implements Plugin { - const { savedObjects, uiSettings } = core; + public async start(core: CoreStart, { telemetryCollectionManager }: TelemetryPluginsDepsStart) { + const { savedObjects, uiSettings, elasticsearch } = core; this.savedObjectsClient = savedObjects.createInternalRepository(); const savedObjectsClient = new SavedObjectsClient(this.savedObjectsClient); this.uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); + this.elasticsearchClient = elasticsearch.client; try { await handleOldSettings(savedObjectsClient, this.uiSettingsClient); diff --git a/src/plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js b/src/plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js deleted file mode 100644 index d1354608385f6..0000000000000 --- a/src/plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { TIMEOUT } from '../constants'; -import { getClusterStats } from '../get_cluster_stats'; - -export function mockGetClusterStats(callCluster, clusterStats, req) { - callCluster - .withArgs(req, 'cluster.stats', { - timeout: TIMEOUT, - }) - .returns(clusterStats); - - callCluster - .withArgs('cluster.stats', { - timeout: TIMEOUT, - }) - .returns(clusterStats); -} - -describe.skip('get_cluster_stats', () => { - it('uses callCluster to get cluster.stats API', async () => { - const callCluster = sinon.stub(); - const response = Promise.resolve({}); - - mockGetClusterStats(callCluster, response); - - expect(getClusterStats(callCluster)).to.be(response); - }); -}); diff --git a/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js b/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js deleted file mode 100644 index 8541745faea3b..0000000000000 --- a/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { merge, omit } from 'lodash'; - -import { TIMEOUT } from '../constants'; -import { mockGetClusterInfo } from './get_cluster_info'; -import { mockGetClusterStats } from './get_cluster_stats'; - -import { getLocalStats, handleLocalStats } from '../get_local_stats'; - -const mockUsageCollection = (kibanaUsage = {}) => ({ - bulkFetch: () => kibanaUsage, - toObject: (data) => data, -}); - -const getMockServer = (getCluster = sinon.stub()) => ({ - log(tags, message) { - console.log({ tags, message }); - }, - config() { - return { - get(item) { - switch (item) { - case 'pkg.version': - return '8675309-snapshot'; - default: - throw Error(`unexpected config.get('${item}') received.`); - } - }, - }; - }, - plugins: { - elasticsearch: { getCluster }, - }, -}); -function mockGetNodesUsage(callCluster, nodesUsage, req) { - callCluster - .withArgs( - req, - { - method: 'GET', - path: '/_nodes/usage', - query: { - timeout: TIMEOUT, - }, - }, - 'transport.request' - ) - .returns(nodesUsage); -} - -function mockGetLocalStats(callCluster, clusterInfo, clusterStats, nodesUsage, req) { - mockGetClusterInfo(callCluster, clusterInfo, req); - mockGetClusterStats(callCluster, clusterStats, req); - mockGetNodesUsage(callCluster, nodesUsage, req); -} - -describe('get_local_stats', () => { - const clusterUuid = 'abc123'; - const clusterName = 'my-cool-cluster'; - const version = '2.3.4'; - const clusterInfo = { - cluster_uuid: clusterUuid, - cluster_name: clusterName, - version: { - number: version, - }, - }; - const nodesUsage = [ - { - node_id: 'some_node_id', - timestamp: 1588617023177, - since: 1588616945163, - rest_actions: { - nodes_usage_action: 1, - create_index_action: 1, - document_get_action: 1, - search_action: 19, - nodes_info_action: 36, - }, - aggregations: { - terms: { - bytes: 2, - }, - scripted_metric: { - other: 7, - }, - }, - }, - ]; - const clusterStats = { - _nodes: { failed: 123 }, - cluster_name: 'real-cool', - indices: { totally: 456 }, - nodes: { yup: 'abc' }, - random: 123, - }; - - const kibana = { - kibana: { - great: 'googlymoogly', - versions: [{ version: '8675309', count: 1 }], - }, - kibana_stats: { - os: { - platform: 'rocky', - platformRelease: 'iv', - }, - }, - localization: { - locale: 'en', - labelsCount: 0, - integrities: {}, - }, - sun: { chances: 5 }, - clouds: { chances: 95 }, - rain: { chances: 2 }, - snow: { chances: 0 }, - }; - - const clusterStatsWithNodesUsage = { - ...clusterStats, - nodes: merge(clusterStats.nodes, { usage: nodesUsage }), - }; - const combinedStatsResult = { - collection: 'local', - cluster_uuid: clusterUuid, - cluster_name: clusterName, - version, - cluster_stats: omit(clusterStatsWithNodesUsage, '_nodes', 'cluster_name'), - stack_stats: { - kibana: { - great: 'googlymoogly', - count: 1, - indices: 1, - os: { - platforms: [{ platform: 'rocky', count: 1 }], - platformReleases: [{ platformRelease: 'iv', count: 1 }], - }, - versions: [{ version: '8675309', count: 1 }], - plugins: { - localization: { - locale: 'en', - labelsCount: 0, - integrities: {}, - }, - sun: { chances: 5 }, - clouds: { chances: 95 }, - rain: { chances: 2 }, - snow: { chances: 0 }, - }, - }, - }, - }; - - const context = { - logger: console, - version: '8.0.0', - }; - - describe('handleLocalStats', () => { - it('returns expected object without xpack and kibana data', () => { - const result = handleLocalStats( - clusterInfo, - clusterStatsWithNodesUsage, - void 0, - void 0, - context - ); - expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid); - expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name); - expect(result.cluster_stats).to.eql(combinedStatsResult.cluster_stats); - expect(result.version).to.be('2.3.4'); - expect(result.collection).to.be('local'); - expect(result.license).to.be(undefined); - expect(result.stack_stats).to.eql({ kibana: undefined, data: undefined }); - }); - - it('returns expected object with xpack', () => { - const result = handleLocalStats( - clusterInfo, - clusterStatsWithNodesUsage, - void 0, - void 0, - context - ); - const { stack_stats: stack, ...cluster } = result; - expect(cluster.collection).to.be(combinedStatsResult.collection); - expect(cluster.cluster_uuid).to.be(combinedStatsResult.cluster_uuid); - expect(cluster.cluster_name).to.be(combinedStatsResult.cluster_name); - expect(stack.kibana).to.be(undefined); // not mocked for this test - expect(stack.data).to.be(undefined); // not mocked for this test - - expect(cluster.version).to.eql(combinedStatsResult.version); - expect(cluster.cluster_stats).to.eql(combinedStatsResult.cluster_stats); - expect(cluster.license).to.eql(combinedStatsResult.license); - expect(stack.xpack).to.eql(combinedStatsResult.stack_stats.xpack); - }); - }); - - describe.skip('getLocalStats', () => { - it('returns expected object without xpack data when X-Pack fails to respond', async () => { - const callClusterUsageFailed = sinon.stub(); - const usageCollection = mockUsageCollection(); - mockGetLocalStats( - callClusterUsageFailed, - Promise.resolve(clusterInfo), - Promise.resolve(clusterStats), - Promise.resolve(nodesUsage) - ); - const result = await getLocalStats([], { - server: getMockServer(), - callCluster: callClusterUsageFailed, - usageCollection, - }); - expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid); - expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name); - expect(result.cluster_stats).to.eql(combinedStatsResult.cluster_stats); - expect(result.cluster_stats.nodes).to.eql(combinedStatsResult.cluster_stats.nodes); - expect(result.version).to.be('2.3.4'); - expect(result.collection).to.be('local'); - - // license and xpack usage info come from the same cluster call - expect(result.license).to.be(undefined); - expect(result.stack_stats.xpack).to.be(undefined); - }); - - it('returns expected object with xpack and kibana data', async () => { - const callCluster = sinon.stub(); - const usageCollection = mockUsageCollection(kibana); - mockGetLocalStats( - callCluster, - Promise.resolve(clusterInfo), - Promise.resolve(clusterStats), - Promise.resolve(nodesUsage) - ); - - const result = await getLocalStats([], { - server: getMockServer(callCluster), - usageCollection, - callCluster, - }); - - expect(result.stack_stats.xpack).to.eql(combinedStatsResult.stack_stats.xpack); - expect(result.stack_stats.kibana).to.eql(combinedStatsResult.stack_stats.kibana); - }); - }); -}); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.test.ts new file mode 100644 index 0000000000000..459b18d252e17 --- /dev/null +++ b/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.test.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; +import { getClusterInfo } from './get_cluster_info'; + +export function mockGetClusterInfo(clusterInfo: any) { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.info + // @ts-ignore we only care about the response body + .mockResolvedValue( + // @ts-ignore we only care about the response body + { + body: { ...clusterInfo }, + } + ); + return esClient; +} + +describe('get_cluster_info using the elasticsearch client', () => { + it('uses the esClient to get info API', async () => { + const clusterInfo = { + cluster_uuid: '1234', + cluster_name: 'testCluster', + version: { + number: '7.9.2', + build_flavor: 'default', + build_type: 'docker', + build_hash: 'b5ca9c58fb664ca8bf', + build_date: '2020-07-21T16:40:44.668009Z', + build_snapshot: false, + lucene_version: '8.5.1', + minimum_wire_compatibility_version: '6.8.0', + minimum_index_compatibility_version: '6.0.0-beta1', + }, + }; + const esClient = mockGetClusterInfo(clusterInfo); + + expect(await getClusterInfo(esClient)).toStrictEqual(clusterInfo); + }); +}); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts index 4a33356ee9761..407f3325c3a9f 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts @@ -17,7 +17,7 @@ * under the License. */ -import { LegacyAPICaller } from 'kibana/server'; +import { ElasticsearchClient } from 'src/core/server'; // This can be removed when the ES client improves the types export interface ESClusterInfo { @@ -25,24 +25,24 @@ export interface ESClusterInfo { cluster_name: string; version: { number: string; - build_flavor: string; - build_type: string; - build_hash: string; - build_date: string; + build_flavor?: string; + build_type?: string; + build_hash?: string; + build_date?: string; build_snapshot?: boolean; - lucene_version: string; - minimum_wire_compatibility_version: string; - minimum_index_compatibility_version: string; + lucene_version?: string; + minimum_wire_compatibility_version?: string; + minimum_index_compatibility_version?: string; }; } - /** * Get the cluster info from the connected cluster. * * This is the equivalent to GET / * - * @param {function} callCluster The callWithInternalUser handler (exposed for testing) + * @param {function} esClient The asInternalUser handler (exposed for testing) */ -export function getClusterInfo(callCluster: LegacyAPICaller) { - return callCluster('info'); +export async function getClusterInfo(esClient: ElasticsearchClient) { + const { body } = await esClient.info(); + return body; } diff --git a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.test.ts new file mode 100644 index 0000000000000..81551c0c4d93d --- /dev/null +++ b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.test.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; +import { getClusterStats } from './get_cluster_stats'; +import { TIMEOUT } from './constants'; + +export function mockGetClusterStats(clusterStats: any) { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.cluster.stats.mockResolvedValue(clusterStats); + return esClient; +} + +describe('get_cluster_stats', () => { + it('uses the esClient to get the response from the `cluster.stats` API', async () => { + const response = Promise.resolve({ body: { cluster_uuid: '1234' } }); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.cluster.stats.mockImplementationOnce( + // @ts-ignore the method only cares about the response body + async (_params = { timeout: TIMEOUT }) => { + return response; + } + ); + const result = getClusterStats(esClient); + expect(esClient.cluster.stats).toHaveBeenCalledWith({ timeout: TIMEOUT }); + expect(result).toStrictEqual(response); + }); +}); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts index d7c0110a99c6f..d2a64e4878679 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts @@ -18,23 +18,23 @@ */ import { ClusterDetailsGetter } from 'src/plugins/telemetry_collection_manager/server'; -import { LegacyAPICaller } from 'kibana/server'; +import { ElasticsearchClient } from 'src/core/server'; import { TIMEOUT } from './constants'; /** * Get the cluster stats from the connected cluster. * * This is the equivalent to GET /_cluster/stats?timeout=30s. */ -export async function getClusterStats(callCluster: LegacyAPICaller) { - return await callCluster('cluster.stats', { - timeout: TIMEOUT, - }); +export async function getClusterStats(esClient: ElasticsearchClient) { + const { body } = await esClient.cluster.stats({ timeout: TIMEOUT }); + return body; } /** * Get the cluster uuids from the connected cluster. */ -export const getClusterUuids: ClusterDetailsGetter = async ({ callCluster }) => { - const result = await getClusterStats(callCluster); - return [{ clusterUuid: result.cluster_uuid }]; +export const getClusterUuids: ClusterDetailsGetter = async ({ esClient }) => { + const { body } = await esClient.cluster.stats({ timeout: TIMEOUT }); + + return [{ clusterUuid: body.cluster_uuid }]; }; diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts index dee718decdc1f..bb5eb7f6b726d 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts @@ -19,6 +19,7 @@ import { buildDataTelemetryPayload, getDataTelemetry } from './get_data_telemetry'; import { DATA_DATASETS_INDEX_PATTERNS, DATA_DATASETS_INDEX_PATTERNS_UNIQUE } from './constants'; +import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; describe('get_data_telemetry', () => { describe('DATA_DATASETS_INDEX_PATTERNS', () => { @@ -195,13 +196,15 @@ describe('get_data_telemetry', () => { describe('getDataTelemetry', () => { test('it returns the base payload (all 0s) because no indices are found', async () => { - const callCluster = mockCallCluster(); - await expect(getDataTelemetry(callCluster)).resolves.toStrictEqual([]); + const esClient = mockEsClient(); + await expect(getDataTelemetry(esClient)).resolves.toStrictEqual([]); + expect(esClient.indices.getMapping).toHaveBeenCalledTimes(1); + expect(esClient.indices.stats).toHaveBeenCalledTimes(1); }); test('can only see the index mappings, but not the stats', async () => { - const callCluster = mockCallCluster(['filebeat-12314']); - await expect(getDataTelemetry(callCluster)).resolves.toStrictEqual([ + const esClient = mockEsClient(['filebeat-12314']); + await expect(getDataTelemetry(esClient)).resolves.toStrictEqual([ { pattern_name: 'filebeat', shipper: 'filebeat', @@ -209,10 +212,12 @@ describe('get_data_telemetry', () => { ecs_index_count: 0, }, ]); + expect(esClient.indices.getMapping).toHaveBeenCalledTimes(1); + expect(esClient.indices.stats).toHaveBeenCalledTimes(1); }); test('can see the mappings and the stats', async () => { - const callCluster = mockCallCluster( + const esClient = mockEsClient( ['filebeat-12314'], { isECS: true }, { @@ -221,7 +226,7 @@ describe('get_data_telemetry', () => { }, } ); - await expect(getDataTelemetry(callCluster)).resolves.toStrictEqual([ + await expect(getDataTelemetry(esClient)).resolves.toStrictEqual([ { pattern_name: 'filebeat', shipper: 'filebeat', @@ -234,7 +239,7 @@ describe('get_data_telemetry', () => { }); test('find an index that does not match any index pattern but has mappings metadata', async () => { - const callCluster = mockCallCluster( + const esClient = mockEsClient( ['cannot_match_anything'], { isECS: true, dataStreamType: 'traces', shipper: 'my-beat' }, { @@ -245,7 +250,7 @@ describe('get_data_telemetry', () => { }, } ); - await expect(getDataTelemetry(callCluster)).resolves.toStrictEqual([ + await expect(getDataTelemetry(esClient)).resolves.toStrictEqual([ { data_stream: { dataset: undefined, type: 'traces' }, shipper: 'my-beat', @@ -258,45 +263,51 @@ describe('get_data_telemetry', () => { }); test('return empty array when there is an error', async () => { - const callCluster = jest.fn().mockRejectedValue(new Error('Something went terribly wrong')); - await expect(getDataTelemetry(callCluster)).resolves.toStrictEqual([]); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.indices.getMapping.mockRejectedValue(new Error('Something went terribly wrong')); + esClient.indices.stats.mockRejectedValue(new Error('Something went terribly wrong')); + await expect(getDataTelemetry(esClient)).resolves.toStrictEqual([]); }); }); }); - -function mockCallCluster( - indicesMappings: string[] = [], +function mockEsClient( + indicesMappings: string[] = [], // an array of `indices` to get mappings from. { isECS = false, dataStreamDataset = '', dataStreamType = '', shipper = '' } = {}, indexStats: any = {} ) { - return jest.fn().mockImplementation(async (method: string, opts: any) => { - if (method === 'indices.getMapping') { - return Object.fromEntries( - indicesMappings.map((index) => [ - index, - { - mappings: { - ...(shipper && { _meta: { beat: shipper } }), - properties: { - ...(isECS && { ecs: { properties: { version: { type: 'keyword' } } } }), - ...((dataStreamType || dataStreamDataset) && { - data_stream: { - properties: { - ...(dataStreamDataset && { - dataset: { type: 'constant_keyword', value: dataStreamDataset }, - }), - ...(dataStreamType && { - type: { type: 'constant_keyword', value: dataStreamType }, - }), - }, + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + // @ts-ignore + esClient.indices.getMapping.mockImplementationOnce(async () => { + const body = Object.fromEntries( + indicesMappings.map((index) => [ + index, + { + mappings: { + ...(shipper && { _meta: { beat: shipper } }), + properties: { + ...(isECS && { ecs: { properties: { version: { type: 'keyword' } } } }), + ...((dataStreamType || dataStreamDataset) && { + data_stream: { + properties: { + ...(dataStreamDataset && { + dataset: { type: 'constant_keyword', value: dataStreamDataset }, + }), + ...(dataStreamType && { + type: { type: 'constant_keyword', value: dataStreamType }, + }), }, - }), - }, + }, + }), }, }, - ]) - ); - } - return indexStats; + }, + ]) + ); + return { body }; + }); + // @ts-ignore + esClient.indices.stats.mockImplementationOnce(async () => { + return { body: indexStats }; }); + return esClient; } diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts index f4734dde251cc..67769793cbfdf 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ +import { ElasticsearchClient } from 'src/core/server'; -import { LegacyAPICaller } from 'kibana/server'; import { DATA_DATASETS_INDEX_PATTERNS_UNIQUE, DataPatternName, @@ -224,42 +224,50 @@ interface IndexMappings { }; } -export async function getDataTelemetry(callCluster: LegacyAPICaller) { +export async function getDataTelemetry(esClient: ElasticsearchClient) { try { const index = [ ...DATA_DATASETS_INDEX_PATTERNS_UNIQUE.map(({ pattern }) => pattern), '*-*-*', // Include data-streams aliases `{type}-{dataset}-{namespace}` ]; - const [indexMappings, indexStats]: [IndexMappings, IndexStats] = await Promise.all([ + const indexMappingsParams: { index: string; filter_path: string[] } = { // GET */_mapping?filter_path=*.mappings._meta.beat,*.mappings.properties.ecs.properties.version.type,*.mappings.properties.dataset.properties.type.value,*.mappings.properties.dataset.properties.name.value - callCluster('indices.getMapping', { - index: '*', // Request all indices because filter_path already filters out the indices without any of those fields - filterPath: [ - // _meta.beat tells the shipper - '*.mappings._meta.beat', - // _meta.package.name tells the Ingest Manager's package - '*.mappings._meta.package.name', - // _meta.managed_by is usually populated by Ingest Manager for the UI to identify it - '*.mappings._meta.managed_by', - // Does it have `ecs.version` in the mappings? => It follows the ECS conventions - '*.mappings.properties.ecs.properties.version.type', + index: '*', // Request all indices because filter_path already filters out the indices without any of those fields + filter_path: [ + // _meta.beat tells the shipper + '*.mappings._meta.beat', + // _meta.package.name tells the Ingest Manager's package + '*.mappings._meta.package.name', + // _meta.managed_by is usually populated by Ingest Manager for the UI to identify it + '*.mappings._meta.managed_by', + // Does it have `ecs.version` in the mappings? => It follows the ECS conventions + '*.mappings.properties.ecs.properties.version.type', - // If `data_stream.type` is a `constant_keyword`, it can be reported as a type - '*.mappings.properties.data_stream.properties.type.value', - // If `data_stream.dataset` is a `constant_keyword`, it can be reported as the dataset - '*.mappings.properties.data_stream.properties.dataset.value', - ], - }), + // If `data_stream.type` is a `constant_keyword`, it can be reported as a type + '*.mappings.properties.data_stream.properties.type.value', + // If `data_stream.dataset` is a `constant_keyword`, it can be reported as the dataset + '*.mappings.properties.data_stream.properties.dataset.value', + ], + }; + const indicesStatsParams: { + index: string | string[] | undefined; + level: 'cluster' | 'indices' | 'shards' | undefined; + metric: string[]; + filter_path: string[]; + } = { // GET /_stats/docs,store?level=indices&filter_path=indices.*.total - callCluster('indices.stats', { - index, - level: 'indices', - metric: ['docs', 'store'], - filterPath: ['indices.*.total'], - }), + index, + level: 'indices', + metric: ['docs', 'store'], + filter_path: ['indices.*.total'], + }; + const [{ body: indexMappings }, { body: indexStats }] = await Promise.all([ + esClient.indices.getMapping(indexMappingsParams), + esClient.indices.stats(indicesStatsParams), ]); const indexNames = Object.keys({ ...indexMappings, ...indexStats?.indices }); + const indices = indexNames.map((name) => { const baseIndexInfo = { name, diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts index d056d1c9f299f..0e2ab98a24cba 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts @@ -20,8 +20,8 @@ export { DATA_TELEMETRY_ID } from './constants'; export { - DataTelemetryIndex, - DataTelemetryPayload, getDataTelemetry, buildDataTelemetryPayload, + DataTelemetryPayload, + DataTelemetryIndex, } from './get_data_telemetry'; diff --git a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts index 5d27774a630a5..0ef9815a4eadb 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts @@ -21,6 +21,7 @@ import { omit } from 'lodash'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { LegacyAPICaller } from 'kibana/server'; import { StatsCollectionContext } from 'src/plugins/telemetry_collection_manager/server'; +import { ElasticsearchClient } from 'src/core/server'; export interface KibanaUsageStats { kibana: { @@ -48,7 +49,6 @@ export function handleKibanaStats( logger.warn('No Kibana stats returned from usage collectors'); return; } - const { kibana, kibana_stats: kibanaStats, ...plugins } = response; const os = { @@ -83,8 +83,9 @@ export function handleKibanaStats( export async function getKibana( usageCollection: UsageCollectionSetup, - callWithInternalUser: LegacyAPICaller + callWithInternalUser: LegacyAPICaller, + asInternalUser: ElasticsearchClient ): Promise { - const usage = await usageCollection.bulkFetch(callWithInternalUser); + const usage = await usageCollection.bulkFetch(callWithInternalUser, asInternalUser); return usageCollection.toObject(usage); } diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts index d41904c6d8e0e..879416cda62fc 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts @@ -17,44 +17,41 @@ * under the License. */ -import { LegacyAPICaller } from 'kibana/server'; import { ESLicense, LicenseGetter } from 'src/plugins/telemetry_collection_manager/server'; +import { ElasticsearchClient } from 'src/core/server'; let cachedLicense: ESLicense | undefined; -function fetchLicense(callCluster: LegacyAPICaller, local: boolean) { - return callCluster<{ license: ESLicense }>('transport.request', { - method: 'GET', - path: '/_license', - query: { - local, - // For versions >= 7.6 and < 8.0, this flag is needed otherwise 'platinum' is returned for 'enterprise' license. - accept_enterprise: 'true', - }, +async function fetchLicense(esClient: ElasticsearchClient, local: boolean) { + const { body } = await esClient.license.get({ + local, + // For versions >= 7.6 and < 8.0, this flag is needed otherwise 'platinum' is returned for 'enterprise' license. + accept_enterprise: true, }); + return body; } - /** * Get the cluster's license from the connected node. * - * This is the equivalent of GET /_license?local=true . + * This is the equivalent of GET /_license?local=true&accept_enterprise=true. * * Like any X-Pack related API, X-Pack must installed for this to work. + * + * In OSS we'll get a 400 response using the new elasticsearch client. */ -async function getLicenseFromLocalOrMaster(callCluster: LegacyAPICaller) { - // Fetching the local license is cheaper than getting it from the master and good enough - const { license } = await fetchLicense(callCluster, true).catch(async (err) => { +async function getLicenseFromLocalOrMaster(esClient: ElasticsearchClient) { + // Fetching the local license is cheaper than getting it from the master node and good enough + const { license } = await fetchLicense(esClient, true).catch(async (err) => { if (cachedLicense) { try { // Fallback to the master node's license info - const response = await fetchLicense(callCluster, false); + const response = await fetchLicense(esClient, false); return response; } catch (masterError) { - if (masterError.statusCode === 404) { + if ([400, 404].includes(masterError.statusCode)) { // If the master node does not have a license, we can assume there is no license cachedLicense = undefined; } else { - // Any other errors from the master node, throw and do not send any telemetry throw err; } } @@ -68,9 +65,8 @@ async function getLicenseFromLocalOrMaster(callCluster: LegacyAPICaller) { return license; } -export const getLocalLicense: LicenseGetter = async (clustersDetails, { callCluster }) => { - const license = await getLicenseFromLocalOrMaster(callCluster); - +export const getLocalLicense: LicenseGetter = async (clustersDetails, { esClient }) => { + const license = await getLicenseFromLocalOrMaster(esClient); // It should be called only with 1 cluster element in the clustersDetails array, but doing reduce just in case. return clustersDetails.reduce((acc, { clusterUuid }) => ({ ...acc, [clusterUuid]: license }), {}); }; diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts new file mode 100644 index 0000000000000..0c8b0b249f7d1 --- /dev/null +++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts @@ -0,0 +1,259 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { merge, omit } from 'lodash'; + +import { getLocalStats, handleLocalStats } from './get_local_stats'; +import { usageCollectionPluginMock } from '../../../usage_collection/server/mocks'; +import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; + +function mockUsageCollection(kibanaUsage = {}) { + const usageCollection = usageCollectionPluginMock.createSetupContract(); + usageCollection.bulkFetch = jest.fn().mockResolvedValue(kibanaUsage); + usageCollection.toObject = jest.fn().mockImplementation((data: any) => data); + return usageCollection; +} +// set up successful call mocks for info, cluster stats, nodes usage and data telemetry +function mockGetLocalStats(clusterInfo: any, clusterStats: any) { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.info + // @ts-ignore we only care about the response body + .mockResolvedValue( + // @ts-ignore we only care about the response body + { + body: { ...clusterInfo }, + } + ); + esClient.cluster.stats + // @ts-ignore we only care about the response body + .mockResolvedValue({ body: { ...clusterStats } }); + esClient.nodes.usage.mockResolvedValue( + // @ts-ignore we only care about the response body + { + body: { + cluster_name: 'testCluster', + nodes: { + some_node_id: { + timestamp: 1588617023177, + since: 1588616945163, + rest_actions: { + nodes_usage_action: 1, + create_index_action: 1, + document_get_action: 1, + search_action: 19, + nodes_info_action: 36, + }, + aggregations: { + terms: { + bytes: 2, + }, + scripted_metric: { + other: 7, + }, + }, + }, + }, + }, + } + ); + // @ts-ignore we only care about the response body + esClient.indices.getMapping.mockResolvedValue({ body: { mappings: {} } }); + // @ts-ignore we only care about the response body + esClient.indices.stats.mockResolvedValue({ body: { indices: {} } }); + return esClient; +} + +describe('get_local_stats', () => { + const clusterUuid = 'abc123'; + const clusterName = 'my-cool-cluster'; + const version = '2.3.4'; + const clusterInfo = { + cluster_uuid: clusterUuid, + cluster_name: clusterName, + version: { number: version }, + }; + const nodesUsage = [ + { + node_id: 'some_node_id', + timestamp: 1588617023177, + since: 1588616945163, + rest_actions: { + nodes_usage_action: 1, + create_index_action: 1, + document_get_action: 1, + search_action: 19, + nodes_info_action: 36, + }, + aggregations: { + terms: { + bytes: 2, + }, + scripted_metric: { + other: 7, + }, + }, + }, + ]; + const clusterStats = { + _nodes: { failed: 123 }, + cluster_name: 'real-cool', + indices: { totally: 456 }, + nodes: { yup: 'abc' }, + random: 123, + }; + + const kibana = { + kibana: { + great: 'googlymoogly', + versions: [{ version: '8675309', count: 1 }], + }, + kibana_stats: { + os: { + platform: 'rocky', + platformRelease: 'iv', + }, + }, + localization: { + locale: 'en', + labelsCount: 0, + integrities: {}, + }, + sun: { chances: 5 }, + clouds: { chances: 95 }, + rain: { chances: 2 }, + snow: { chances: 0 }, + }; + + const clusterStatsWithNodesUsage = { + ...clusterStats, + nodes: merge(clusterStats.nodes, { usage: { nodes: nodesUsage } }), + }; + + const combinedStatsResult = { + collection: 'local', + cluster_uuid: clusterUuid, + cluster_name: clusterName, + version, + cluster_stats: omit(clusterStatsWithNodesUsage, '_nodes', 'cluster_name'), + stack_stats: { + kibana: { + great: 'googlymoogly', + count: 1, + indices: 1, + os: { + platforms: [{ platform: 'rocky', count: 1 }], + platformReleases: [{ platformRelease: 'iv', count: 1 }], + }, + versions: [{ version: '8675309', count: 1 }], + plugins: { + localization: { + locale: 'en', + labelsCount: 0, + integrities: {}, + }, + sun: { chances: 5 }, + clouds: { chances: 95 }, + rain: { chances: 2 }, + snow: { chances: 0 }, + }, + }, + }, + }; + + const context = { + logger: console, + version: '8.0.0', + }; + + describe('handleLocalStats', () => { + it('returns expected object without xpack or kibana data', () => { + const result = handleLocalStats( + clusterInfo, + clusterStatsWithNodesUsage, + void 0, + void 0, + context + ); + expect(result.cluster_uuid).toStrictEqual(combinedStatsResult.cluster_uuid); + expect(result.cluster_name).toStrictEqual(combinedStatsResult.cluster_name); + expect(result.cluster_stats).toStrictEqual(combinedStatsResult.cluster_stats); + expect(result.version).toEqual('2.3.4'); + expect(result.collection).toEqual('local'); + expect(Object.keys(result)).not.toContain('license'); + expect(result.stack_stats).toEqual({ kibana: undefined, data: undefined }); + }); + + it('returns expected object with xpack', () => { + const result = handleLocalStats( + clusterInfo, + clusterStatsWithNodesUsage, + void 0, + void 0, + context + ); + + const { stack_stats: stack, ...cluster } = result; + expect(cluster.collection).toBe(combinedStatsResult.collection); + expect(cluster.cluster_uuid).toBe(combinedStatsResult.cluster_uuid); + expect(cluster.cluster_name).toBe(combinedStatsResult.cluster_name); + expect(stack.kibana).toBe(undefined); // not mocked for this test + expect(stack.data).toBe(undefined); // not mocked for this test + + expect(cluster.version).toEqual(combinedStatsResult.version); + expect(cluster.cluster_stats).toEqual(combinedStatsResult.cluster_stats); + expect(Object.keys(cluster).indexOf('license')).toBeLessThan(0); + expect(Object.keys(stack).indexOf('xpack')).toBeLessThan(0); + }); + }); + + describe('getLocalStats', () => { + it('returns expected object with kibana data', async () => { + const callCluster = jest.fn(); + const usageCollection = mockUsageCollection(kibana); + const esClient = mockGetLocalStats(clusterInfo, clusterStats); + const response = await getLocalStats( + [{ clusterUuid: 'abc123' }], + { callCluster, usageCollection, esClient, start: '', end: '' }, + context + ); + const result = response[0]; + expect(result.cluster_uuid).toEqual(combinedStatsResult.cluster_uuid); + expect(result.cluster_name).toEqual(combinedStatsResult.cluster_name); + expect(result.cluster_stats).toEqual(combinedStatsResult.cluster_stats); + expect(result.cluster_stats.nodes).toEqual(combinedStatsResult.cluster_stats.nodes); + expect(result.version).toBe('2.3.4'); + expect(result.collection).toBe('local'); + expect(Object.keys(result).indexOf('license')).toBeLessThan(0); + expect(Object.keys(result.stack_stats).indexOf('xpack')).toBeLessThan(0); + }); + + it('returns an empty array when no cluster uuid is provided', async () => { + const callCluster = jest.fn(); + const usageCollection = mockUsageCollection(kibana); + const esClient = mockGetLocalStats(clusterInfo, clusterStats); + const response = await getLocalStats( + [], + { callCluster, usageCollection, esClient, start: '', end: '' }, + context + ); + expect(response).toBeDefined(); + expect(response.length).toEqual(0); + }); + }); +}); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts index 98c83a3394628..6244c6fac51d3 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts @@ -40,8 +40,8 @@ export function handleLocalStats( // eslint-disable-next-line @typescript-eslint/naming-convention { cluster_name, cluster_uuid, version }: ESClusterInfo, { _nodes, cluster_name: clusterName, ...clusterStats }: any, - kibana: KibanaUsageStats, - dataTelemetry: DataTelemetryPayload, + kibana: KibanaUsageStats | undefined, + dataTelemetry: DataTelemetryPayload | undefined, context: StatsCollectionContext ) { return { @@ -62,22 +62,25 @@ export type TelemetryLocalStats = ReturnType; /** * Get statistics for all products joined by Elasticsearch cluster. + * @param {Array} cluster uuids + * @param {Object} config contains the new esClient already scoped contains usageCollection, callCluster, esClient, start, end + * @param {Object} StatsCollectionContext contains logger and version (string) */ export const getLocalStats: StatsGetter<{}, TelemetryLocalStats> = async ( - clustersDetails, - config, - context + clustersDetails, // array of cluster uuid's + config, // contains the new esClient already scoped contains usageCollection, callCluster, esClient, start, end + context // StatsCollectionContext contains logger and version (string) ) => { - const { callCluster, usageCollection } = config; + const { callCluster, usageCollection, esClient } = config; return await Promise.all( clustersDetails.map(async (clustersDetail) => { const [clusterInfo, clusterStats, nodesUsage, kibana, dataTelemetry] = await Promise.all([ - getClusterInfo(callCluster), // cluster info - getClusterStats(callCluster), // cluster stats (not to be confused with cluster _state_) - getNodesUsage(callCluster), // nodes_usage info - getKibana(usageCollection, callCluster), - getDataTelemetry(callCluster), + getClusterInfo(esClient), // cluster info + getClusterStats(esClient), // cluster stats (not to be confused with cluster _state_) + getNodesUsage(esClient), // nodes_usage info + getKibana(usageCollection, callCluster, esClient), + getDataTelemetry(esClient), ]); return handleLocalStats( clusterInfo, diff --git a/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.test.ts index 4e4b0e11b7979..acf403ba25447 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.test.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.test.ts @@ -19,6 +19,7 @@ import { getNodesUsage } from './get_nodes_usage'; import { TIMEOUT } from './constants'; +import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; const mockedNodesFetchResponse = { cluster_name: 'test cluster', @@ -44,37 +45,35 @@ const mockedNodesFetchResponse = { }, }, }; + describe('get_nodes_usage', () => { - it('calls fetchNodesUsage', async () => { - const callCluster = jest.fn(); - callCluster.mockResolvedValueOnce(mockedNodesFetchResponse); - await getNodesUsage(callCluster); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_nodes/usage', - method: 'GET', - query: { - timeout: TIMEOUT, - }, - }); - }); - it('returns a modified array of node usage data', async () => { - const callCluster = jest.fn(); - callCluster.mockResolvedValueOnce(mockedNodesFetchResponse); - const result = await getNodesUsage(callCluster); - expect(result.nodes).toEqual([ - { - aggregations: { scripted_metric: { other: 7 }, terms: { bytes: 2 } }, - node_id: 'some_node_id', - rest_actions: { - create_index_action: 1, - document_get_action: 1, - nodes_info_action: 36, - nodes_usage_action: 1, - search_action: 19, + it('returns a modified array of nodes usage data', async () => { + const response = Promise.resolve({ body: mockedNodesFetchResponse }); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.nodes.usage.mockImplementationOnce( + // @ts-ignore + async (_params = { timeout: TIMEOUT }) => { + return response; + } + ); + const item = await getNodesUsage(esClient); + expect(esClient.nodes.usage).toHaveBeenCalledWith({ timeout: TIMEOUT }); + expect(item).toStrictEqual({ + nodes: [ + { + aggregations: { scripted_metric: { other: 7 }, terms: { bytes: 2 } }, + node_id: 'some_node_id', + rest_actions: { + create_index_action: 1, + document_get_action: 1, + nodes_info_action: 36, + nodes_usage_action: 1, + search_action: 19, + }, + since: 1588616945163, + timestamp: 1588617023177, }, - since: 1588616945163, - timestamp: 1588617023177, - }, - ]); + ], + }); }); }); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts index c5c110fbb4149..959840d0020a2 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { LegacyAPICaller } from 'kibana/server'; +import { ElasticsearchClient } from 'src/core/server'; import { TIMEOUT } from './constants'; export interface NodeAggregation { @@ -44,7 +44,7 @@ export interface NodesFeatureUsageResponse { } export type NodesUsageGetter = ( - callCluster: LegacyAPICaller + esClient: ElasticsearchClient ) => Promise<{ nodes: NodeObj[] | Array<{}> }>; /** * Get the nodes usage data from the connected cluster. @@ -54,16 +54,12 @@ export type NodesUsageGetter = ( * The Nodes usage API was introduced in v6.0.0 */ export async function fetchNodesUsage( - callCluster: LegacyAPICaller + esClient: ElasticsearchClient ): Promise { - const response = await callCluster('transport.request', { - method: 'GET', - path: '/_nodes/usage', - query: { - timeout: TIMEOUT, - }, + const { body } = await esClient.nodes.usage({ + timeout: TIMEOUT, }); - return response; + return body; } /** @@ -71,8 +67,8 @@ export async function fetchNodesUsage( * @param callCluster APICaller * @returns Object containing array of modified usage information with the node_id nested within the data for that node. */ -export const getNodesUsage: NodesUsageGetter = async (callCluster) => { - const result = await fetchNodesUsage(callCluster); +export const getNodesUsage: NodesUsageGetter = async (esClient) => { + const result = await fetchNodesUsage(esClient); const transformedNodes = Object.entries(result?.nodes || {}).map(([key, value]) => ({ ...(value as NodeObj), node_id: key, diff --git a/src/plugins/telemetry/server/telemetry_collection/register_collection.ts b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts index 438fcadad9255..9dac4900f5f10 100644 --- a/src/plugins/telemetry/server/telemetry_collection/register_collection.ts +++ b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts @@ -38,16 +38,19 @@ import { ILegacyClusterClient } from 'kibana/server'; import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; +import { IClusterClient } from '../../../../../src/core/server'; import { getLocalStats } from './get_local_stats'; import { getClusterUuids } from './get_cluster_stats'; import { getLocalLicense } from './get_local_license'; export function registerCollection( telemetryCollectionManager: TelemetryCollectionManagerPluginSetup, - esCluster: ILegacyClusterClient + esCluster: ILegacyClusterClient, + esClientGetter: () => IClusterClient | undefined ) { telemetryCollectionManager.setCollection({ esCluster, + esClientGetter, title: 'local', priority: 0, statsGetter: getLocalStats, diff --git a/src/plugins/telemetry_collection_manager/server/plugin.ts b/src/plugins/telemetry_collection_manager/server/plugin.ts index 051bb3a11cb16..e54e7451a670a 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.ts @@ -24,6 +24,7 @@ import { CoreStart, Plugin, Logger, + IClusterClient, } from '../../../core/server'; import { @@ -86,6 +87,7 @@ export class TelemetryCollectionManagerPlugin title, priority, esCluster, + esClientGetter, statsGetter, clusterDetailsGetter, licenseGetter, @@ -105,6 +107,9 @@ export class TelemetryCollectionManagerPlugin if (!esCluster) { throw Error('esCluster name must be set for the getCluster method.'); } + if (!esClientGetter) { + throw Error('esClientGetter method not set.'); + } if (!clusterDetailsGetter) { throw Error('Cluster UUIds method is not set.'); } @@ -118,6 +123,7 @@ export class TelemetryCollectionManagerPlugin clusterDetailsGetter, esCluster, title, + esClientGetter, }); this.usageGetterMethodPriority = priority; } @@ -126,6 +132,7 @@ export class TelemetryCollectionManagerPlugin private getStatsCollectionConfig( config: StatsGetterConfig, collection: Collection, + collectionEsClient: IClusterClient, usageCollection: UsageCollectionSetup ): StatsCollectionConfig { const { start, end, request } = config; @@ -133,8 +140,11 @@ export class TelemetryCollectionManagerPlugin const callCluster = config.unencrypted ? collection.esCluster.asScoped(request).callAsCurrentUser : collection.esCluster.callAsInternalUser; - - return { callCluster, start, end, usageCollection }; + // Scope the new elasticsearch Client appropriately and pass to the stats collection config + const esClient = config.unencrypted + ? collectionEsClient.asScoped(config.request).asCurrentUser + : collectionEsClient.asInternalUser; + return { callCluster, start, end, usageCollection, esClient }; } private async getOptInStats(optInStatus: boolean, config: StatsGetterConfig) { @@ -142,27 +152,33 @@ export class TelemetryCollectionManagerPlugin return []; } for (const collection of this.collections) { - const statsCollectionConfig = this.getStatsCollectionConfig( - config, - collection, - this.usageCollection - ); - try { - const optInStats = await this.getOptInStatsForCollection( + // first fetch the client and make sure it's not undefined. + const collectionEsClient = collection.esClientGetter(); + if (collectionEsClient !== undefined) { + const statsCollectionConfig = this.getStatsCollectionConfig( + config, collection, - optInStatus, - statsCollectionConfig + collectionEsClient, + this.usageCollection ); - if (optInStats && optInStats.length) { - this.logger.debug(`Got Opt In stats using ${collection.title} collection.`); - if (config.unencrypted) { - return optInStats; + + try { + const optInStats = await this.getOptInStatsForCollection( + collection, + optInStatus, + statsCollectionConfig + ); + if (optInStats && optInStats.length) { + this.logger.debug(`Got Opt In stats using ${collection.title} collection.`); + if (config.unencrypted) { + return optInStats; + } + return encryptTelemetry(optInStats, { useProdKey: this.isDistributable }); } - return encryptTelemetry(optInStats, { useProdKey: this.isDistributable }); + } catch (err) { + this.logger.debug(`Failed to collect any opt in stats with registered collections.`); + // swallow error to try next collection; } - } catch (err) { - this.logger.debug(`Failed to collect any opt in stats with registered collections.`); - // swallow error to try next collection; } } @@ -192,28 +208,32 @@ export class TelemetryCollectionManagerPlugin return []; } for (const collection of this.collections) { - const statsCollectionConfig = this.getStatsCollectionConfig( - config, - collection, - this.usageCollection - ); - try { - const usageData = await this.getUsageForCollection(collection, statsCollectionConfig); - if (usageData.length) { - this.logger.debug(`Got Usage using ${collection.title} collection.`); - if (config.unencrypted) { - return usageData; - } + const collectionEsClient = collection.esClientGetter(); + if (collectionEsClient !== undefined) { + const statsCollectionConfig = this.getStatsCollectionConfig( + config, + collection, + collectionEsClient, + this.usageCollection + ); + try { + const usageData = await this.getUsageForCollection(collection, statsCollectionConfig); + if (usageData.length) { + this.logger.debug(`Got Usage using ${collection.title} collection.`); + if (config.unencrypted) { + return usageData; + } - return encryptTelemetry(usageData.filter(isClusterOptedIn), { - useProdKey: this.isDistributable, - }); + return encryptTelemetry(usageData.filter(isClusterOptedIn), { + useProdKey: this.isDistributable, + }); + } + } catch (err) { + this.logger.debug( + `Failed to collect any usage with registered collection ${collection.title}.` + ); + // swallow error to try next collection; } - } catch (err) { - this.logger.debug( - `Failed to collect any usage with registered collection ${collection.title}.` - ); - // swallow error to try next collection; } } diff --git a/src/plugins/telemetry_collection_manager/server/types.ts b/src/plugins/telemetry_collection_manager/server/types.ts index 16f96c07fd8ea..44970df30fd16 100644 --- a/src/plugins/telemetry_collection_manager/server/types.ts +++ b/src/plugins/telemetry_collection_manager/server/types.ts @@ -17,8 +17,15 @@ * under the License. */ -import { LegacyAPICaller, Logger, KibanaRequest, ILegacyClusterClient } from 'kibana/server'; +import { + LegacyAPICaller, + Logger, + KibanaRequest, + ILegacyClusterClient, + IClusterClient, +} from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { ElasticsearchClient } from '../../../../src/core/server'; import { TelemetryCollectionManagerPlugin } from './plugin'; export interface TelemetryCollectionManagerPluginSetup { @@ -67,6 +74,7 @@ export interface StatsCollectionConfig { callCluster: LegacyAPICaller; start: string | number; end: string | number; + esClient: ElasticsearchClient; } export interface BasicStatsPayload { @@ -100,7 +108,7 @@ export interface ESLicense { } export interface StatsCollectionContext { - logger: Logger; + logger: Logger | Console; version: string; } @@ -130,6 +138,7 @@ export interface CollectionConfig< title: string; priority: number; esCluster: ILegacyClusterClient; + esClientGetter: () => IClusterClient | undefined; // --> by now we know that the client getter will return the IClusterClient but we assure that through a code check statsGetter: StatsGetter; clusterDetailsGetter: ClusterDetailsGetter; licenseGetter: LicenseGetter; @@ -145,5 +154,6 @@ export interface Collection< licenseGetter: LicenseGetter; clusterDetailsGetter: ClusterDetailsGetter; esCluster: ILegacyClusterClient; + esClientGetter: () => IClusterClient | undefined; // the collection could still return undefined for the es client getter. title: string; } diff --git a/src/plugins/tile_map/public/geohash_layer.js b/src/plugins/tile_map/public/geohash_layer.js index ca2f49a1f31e0..ca992a0d09ec9 100644 --- a/src/plugins/tile_map/public/geohash_layer.js +++ b/src/plugins/tile_map/public/geohash_layer.js @@ -19,14 +19,14 @@ import { min, isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { L, KibanaMapLayer, MapTypes } from '../../maps_legacy/public'; +import { KibanaMapLayer, MapTypes } from '../../maps_legacy/public'; import { HeatmapMarkers } from './markers/heatmap'; import { ScaledCirclesMarkers } from './markers/scaled_circles'; import { ShadedCirclesMarkers } from './markers/shaded_circles'; import { GeohashGridMarkers } from './markers/geohash_grid'; export class GeohashLayer extends KibanaMapLayer { - constructor(featureCollection, featureCollectionMetaData, options, zoom, kibanaMap) { + constructor(featureCollection, featureCollectionMetaData, options, zoom, kibanaMap, leaflet) { super(); this._featureCollection = featureCollection; @@ -35,7 +35,8 @@ export class GeohashLayer extends KibanaMapLayer { this._geohashOptions = options; this._zoom = zoom; this._kibanaMap = kibanaMap; - const geojson = L.geoJson(this._featureCollection); + this._leaflet = leaflet; + const geojson = this._leaflet.geoJson(this._featureCollection); this._bounds = geojson.getBounds(); this._createGeohashMarkers(); this._lastBounds = null; @@ -56,7 +57,8 @@ export class GeohashLayer extends KibanaMapLayer { this._featureCollectionMetaData, markerOptions, this._zoom, - this._kibanaMap + this._kibanaMap, + this._leaflet ); break; case MapTypes.ShadedCircleMarkers: @@ -65,7 +67,8 @@ export class GeohashLayer extends KibanaMapLayer { this._featureCollectionMetaData, markerOptions, this._zoom, - this._kibanaMap + this._kibanaMap, + this._leaflet ); break; case MapTypes.ShadedGeohashGrid: @@ -74,7 +77,8 @@ export class GeohashLayer extends KibanaMapLayer { this._featureCollectionMetaData, markerOptions, this._zoom, - this._kibanaMap + this._kibanaMap, + this._leaflet ); break; case MapTypes.Heatmap: @@ -95,7 +99,8 @@ export class GeohashLayer extends KibanaMapLayer { tooltipFormatter: this._geohashOptions.tooltipFormatter, }, this._zoom, - this._featureCollectionMetaData.max + this._featureCollectionMetaData.max, + this._leaflet ); break; default: @@ -126,9 +131,15 @@ export class GeohashLayer extends KibanaMapLayer { if (this._geohashOptions.fetchBounds) { const geoHashBounds = await this._geohashOptions.fetchBounds(); if (geoHashBounds) { - const northEast = L.latLng(geoHashBounds.top_left.lat, geoHashBounds.bottom_right.lon); - const southWest = L.latLng(geoHashBounds.bottom_right.lat, geoHashBounds.top_left.lon); - return L.latLngBounds(southWest, northEast); + const northEast = this._leaflet.latLng( + geoHashBounds.top_left.lat, + geoHashBounds.bottom_right.lon + ); + const southWest = this._leaflet.latLng( + geoHashBounds.bottom_right.lat, + geoHashBounds.top_left.lon + ); + return this._leaflet.latLngBounds(southWest, northEast); } } diff --git a/src/plugins/tile_map/public/markers/geohash_grid.js b/src/plugins/tile_map/public/markers/geohash_grid.js index 46e7987c601f5..d81e435a6dd5d 100644 --- a/src/plugins/tile_map/public/markers/geohash_grid.js +++ b/src/plugins/tile_map/public/markers/geohash_grid.js @@ -18,11 +18,10 @@ */ import { ScaledCirclesMarkers } from './scaled_circles'; -import { L } from '../../../maps_legacy/public'; export class GeohashGridMarkers extends ScaledCirclesMarkers { getMarkerFunction() { - return function (feature) { + return (feature) => { const geohashRect = feature.properties.geohash_meta.rectangle; // get bounds from northEast[3] and southWest[1] // corners in geohash rectangle @@ -30,7 +29,7 @@ export class GeohashGridMarkers extends ScaledCirclesMarkers { [geohashRect[3][0], geohashRect[3][1]], [geohashRect[1][0], geohashRect[1][1]], ]; - return L.rectangle(corners); + return this._leaflet.rectangle(corners); }; } } diff --git a/src/plugins/tile_map/public/markers/heatmap.js b/src/plugins/tile_map/public/markers/heatmap.js index f2d014797bce0..79bbee16548ac 100644 --- a/src/plugins/tile_map/public/markers/heatmap.js +++ b/src/plugins/tile_map/public/markers/heatmap.js @@ -20,7 +20,6 @@ import _ from 'lodash'; import d3 from 'd3'; import { EventEmitter } from 'events'; -import { L } from '../../../maps_legacy/public'; /** * Map overlay: canvas layer with leaflet.heat plugin @@ -30,17 +29,17 @@ import { L } from '../../../maps_legacy/public'; * @param params {Object} */ export class HeatmapMarkers extends EventEmitter { - constructor(featureCollection, options, zoom, max) { + constructor(featureCollection, options, zoom, max, leaflet) { super(); this._geojsonFeatureCollection = featureCollection; const points = dataToHeatArray(featureCollection, max); - this._leafletLayer = new L.HeatLayer(points, options); + this._leafletLayer = new leaflet.HeatLayer(points, options); this._tooltipFormatter = options.tooltipFormatter; this._zoom = zoom; this._disableTooltips = false; this._getLatLng = _.memoize( function (feature) { - return L.latLng(feature.geometry.coordinates[1], feature.geometry.coordinates[0]); + return leaflet.latLng(feature.geometry.coordinates[1], feature.geometry.coordinates[0]); }, function (feature) { // turn coords into a string for the memoize cache diff --git a/src/plugins/tile_map/public/markers/scaled_circles.js b/src/plugins/tile_map/public/markers/scaled_circles.js index cb111107f6fe3..4a5f1b452580b 100644 --- a/src/plugins/tile_map/public/markers/scaled_circles.js +++ b/src/plugins/tile_map/public/markers/scaled_circles.js @@ -21,7 +21,7 @@ import _ from 'lodash'; import d3 from 'd3'; import $ from 'jquery'; import { EventEmitter } from 'events'; -import { L, colorUtil } from '../../../maps_legacy/public'; +import { colorUtil } from '../../../maps_legacy/public'; import { truncatedColorMaps } from '../../../charts/public'; export class ScaledCirclesMarkers extends EventEmitter { @@ -31,14 +31,13 @@ export class ScaledCirclesMarkers extends EventEmitter { options, targetZoom, kibanaMap, - metricAgg + leaflet ) { super(); this._featureCollection = featureCollection; this._featureCollectionMetaData = featureCollectionMetaData; this._zoom = targetZoom; - this._metricAgg = metricAgg; this._valueFormatter = options.valueFormatter || @@ -55,6 +54,7 @@ export class ScaledCirclesMarkers extends EventEmitter { this._legendColors = null; this._legendQuantizer = null; + this._leaflet = leaflet; this._popups = []; @@ -72,7 +72,7 @@ export class ScaledCirclesMarkers extends EventEmitter { return kibanaMap.isInside(bucketRectBounds); }; } - this._leafletLayer = L.geoJson(null, layerOptions); + this._leafletLayer = this._leaflet.geoJson(null, layerOptions); this._leafletLayer.addData(this._featureCollection); } @@ -143,7 +143,7 @@ export class ScaledCirclesMarkers extends EventEmitter { mouseover: (e) => { const layer = e.target; // bring layer to front if not older browser - if (!L.Browser.ie && !L.Browser.opera) { + if (!this._leaflet.Browser.ie && !this._leaflet.Browser.opera) { layer.bringToFront(); } this._showTooltip(feature); @@ -170,7 +170,10 @@ export class ScaledCirclesMarkers extends EventEmitter { return; } - const latLng = L.latLng(feature.geometry.coordinates[1], feature.geometry.coordinates[0]); + const latLng = this._leaflet.latLng( + feature.geometry.coordinates[1], + feature.geometry.coordinates[0] + ); this.emit('showTooltip', { content: content, position: latLng, @@ -182,7 +185,7 @@ export class ScaledCirclesMarkers extends EventEmitter { return (feature, latlng) => { const value = feature.properties.value; const scaledRadius = this._radiusScale(value) * scaleFactor; - return L.circleMarker(latlng).setRadius(scaledRadius); + return this._leaflet.circleMarker(latlng).setRadius(scaledRadius); }; } diff --git a/src/plugins/tile_map/public/markers/shaded_circles.js b/src/plugins/tile_map/public/markers/shaded_circles.js index 745d0422856c6..3468cab7d8b75 100644 --- a/src/plugins/tile_map/public/markers/shaded_circles.js +++ b/src/plugins/tile_map/public/markers/shaded_circles.js @@ -19,7 +19,6 @@ import _ from 'lodash'; import { ScaledCirclesMarkers } from './scaled_circles'; -import { L } from '../../../maps_legacy/public'; export class ShadedCirclesMarkers extends ScaledCirclesMarkers { getMarkerFunction() { @@ -27,7 +26,7 @@ export class ShadedCirclesMarkers extends ScaledCirclesMarkers { const scaleFactor = 0.8; return (feature, latlng) => { const radius = this._geohashMinDistance(feature) * scaleFactor; - return L.circle(latlng, radius); + return this._leaflet.circle(latlng, radius); }; } @@ -49,12 +48,12 @@ export class ShadedCirclesMarkers extends ScaledCirclesMarkers { // clockwise, each value being an array of [lat, lng] // center lat and southeast lng - const east = L.latLng([centerPoint[0], geohashRect[2][1]]); + const east = this._leaflet.latLng([centerPoint[0], geohashRect[2][1]]); // southwest lat and center lng - const north = L.latLng([geohashRect[3][0], centerPoint[1]]); + const north = this._leaflet.latLng([geohashRect[3][0], centerPoint[1]]); // get latLng of geohash center point - const center = L.latLng([centerPoint[0], centerPoint[1]]); + const center = this._leaflet.latLng([centerPoint[0], centerPoint[1]]); // get smallest radius at center of geohash grid rectangle const eastRadius = Math.floor(center.distanceTo(east)); diff --git a/src/plugins/tile_map/public/plugin.ts b/src/plugins/tile_map/public/plugin.ts index 9a164f8a303f8..07add6901fb49 100644 --- a/src/plugins/tile_map/public/plugin.ts +++ b/src/plugins/tile_map/public/plugin.ts @@ -47,7 +47,7 @@ interface TileMapVisualizationDependencies { getZoomPrecision: any; getPrecision: any; BaseMapsVisualization: any; - serviceSettings: IServiceSettings; + getServiceSettings: () => Promise; } /** @internal */ @@ -81,13 +81,13 @@ export class TileMapPlugin implements Plugin = { getZoomPrecision, getPrecision, BaseMapsVisualization: mapsLegacy.getBaseMapsVis(), uiSettings: core.uiSettings, - serviceSettings, + getServiceSettings, }; expressions.registerFunction(() => createTileMapFn(visualizationDependencies)); diff --git a/src/plugins/tile_map/public/tile_map_type.js b/src/plugins/tile_map/public/tile_map_type.js index f76da26022a77..2b23f345f012e 100644 --- a/src/plugins/tile_map/public/tile_map_type.js +++ b/src/plugins/tile_map/public/tile_map_type.js @@ -28,7 +28,7 @@ import { truncatedColorSchemas } from '../../charts/public'; export function createTileMapTypeDefinition(dependencies) { const CoordinateMapsVisualization = createTileMapVisualization(dependencies); - const { uiSettings, serviceSettings } = dependencies; + const { uiSettings, getServiceSettings } = dependencies; return { name: 'tile_map', @@ -142,6 +142,7 @@ export function createTileMapTypeDefinition(dependencies) { let tmsLayers; try { + const serviceSettings = await getServiceSettings(); tmsLayers = await serviceSettings.getTMSServices(); } catch (e) { return vis; diff --git a/src/plugins/tile_map/public/tile_map_visualization.js b/src/plugins/tile_map/public/tile_map_visualization.js index 2ebb76d05c219..b09a2f3bac48f 100644 --- a/src/plugins/tile_map/public/tile_map_visualization.js +++ b/src/plugins/tile_map/public/tile_map_visualization.js @@ -17,12 +17,42 @@ * under the License. */ -import { get } from 'lodash'; -import { GeohashLayer } from './geohash_layer'; +import { get, round } from 'lodash'; import { getFormatService, getQueryService, getKibanaLegacy } from './services'; -import { scaleBounds, geoContains, mapTooltipProvider } from '../../maps_legacy/public'; +import { + geoContains, + mapTooltipProvider, + lazyLoadMapsLegacyModules, +} from '../../maps_legacy/public'; import { tooltipFormatter } from './tooltip_formatter'; +function scaleBounds(bounds) { + const scale = 0.5; // scale bounds by 50% + + const topLeft = bounds.top_left; + const bottomRight = bounds.bottom_right; + let latDiff = round(Math.abs(topLeft.lat - bottomRight.lat), 5); + const lonDiff = round(Math.abs(bottomRight.lon - topLeft.lon), 5); + // map height can be zero when vis is first created + if (latDiff === 0) latDiff = lonDiff; + + const latDelta = latDiff * scale; + let topLeftLat = round(topLeft.lat, 5) + latDelta; + if (topLeftLat > 90) topLeftLat = 90; + let bottomRightLat = round(bottomRight.lat, 5) - latDelta; + if (bottomRightLat < -90) bottomRightLat = -90; + const lonDelta = lonDiff * scale; + let topLeftLon = round(topLeft.lon, 5) - lonDelta; + if (topLeftLon < -180) topLeftLon = -180; + let bottomRightLon = round(bottomRight.lon, 5) + lonDelta; + if (bottomRightLon > 180) bottomRightLon = 180; + + return { + top_left: { lat: topLeftLat, lon: topLeftLon }, + bottom_right: { lat: bottomRightLat, lon: bottomRightLon }, + }; +} + export const createTileMapVisualization = (dependencies) => { const { getZoomPrecision, getPrecision, BaseMapsVisualization } = dependencies; @@ -147,7 +177,9 @@ export const createTileMapVisualization = (dependencies) => { this._recreateGeohashLayer(); } - _recreateGeohashLayer() { + async _recreateGeohashLayer() { + const { GeohashLayer } = await import('./geohash_layer'); + if (this._geohashLayer) { this._kibanaMap.removeLayer(this._geohashLayer); this._geohashLayer = null; @@ -158,7 +190,8 @@ export const createTileMapVisualization = (dependencies) => { this._geoJsonFeatureCollectionAndMeta.meta, geohashOptions, this._kibanaMap.getZoomLevel(), - this._kibanaMap + this._kibanaMap, + (await lazyLoadMapsLegacyModules()).L ); this._kibanaMap.addLayer(this._geohashLayer); } diff --git a/src/plugins/ui_actions/public/mocks.ts b/src/plugins/ui_actions/public/mocks.ts index 3522ac4941ba0..759430169b613 100644 --- a/src/plugins/ui_actions/public/mocks.ts +++ b/src/plugins/ui_actions/public/mocks.ts @@ -48,6 +48,7 @@ const createStartContract = (): Start => { executeTriggerActions: jest.fn(), fork: jest.fn(), getAction: jest.fn(), + hasAction: jest.fn(), getTrigger: jest.fn(), getTriggerActions: jest.fn((id: TriggerId) => []), getTriggerCompatibleActions: jest.fn(), diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 6028177964fb7..ec5f3afa19c94 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -99,6 +99,10 @@ export class UiActionsService { this.actions.delete(actionId); }; + public readonly hasAction = (actionId: string): boolean => { + return this.actions.has(actionId); + }; + public readonly attachAction = (triggerId: T, actionId: string): void => { const trigger = this.triggers.get(triggerId); diff --git a/src/plugins/usage_collection/README.md b/src/plugins/usage_collection/README.md index 0b1cca07de007..d8edc5bb8d18a 100644 --- a/src/plugins/usage_collection/README.md +++ b/src/plugins/usage_collection/README.md @@ -63,7 +63,7 @@ All you need to provide is a `type` for organizing your fields, `schema` field t total: 'long', }, }, - fetch: async (callCluster: APICluster) => { + fetch: async (callCluster: APICluster, esClient: IClusterClient) => { // query ES and get some data // summarize the data into a model @@ -86,9 +86,9 @@ Some background: - `MY_USAGE_TYPE` can be any string. It usually matches the plugin name. As a safety mechanism, we double check there are no duplicates at the moment of registering the collector. - The `fetch` method needs to support multiple contexts in which it is called. For example, when stats are pulled from a Kibana Metricbeat module, the Beat calls Kibana's stats API to invoke usage collection. -In this case, the `fetch` method is called as a result of an HTTP API request and `callCluster` wraps `callWithRequest`, where the request headers are expected to have read privilege on the entire `.kibana' index. +In this case, the `fetch` method is called as a result of an HTTP API request and `callCluster` wraps `callWithRequest` or `esClient` wraps `asCurrentUser`, where the request headers are expected to have read privilege on the entire `.kibana' index. -Note: there will be many cases where you won't need to use the `callCluster` function that gets passed in to your `fetch` method at all. Your feature might have an accumulating value in server memory, or read something from the OS, or use other clients like a custom SavedObjects client. In that case it's up to the plugin to initialize those clients like the example below: +Note: there will be many cases where you won't need to use the `callCluster` (or `esClient`) function that gets passed in to your `fetch` method at all. Your feature might have an accumulating value in server memory, or read something from the OS, or use other clients like a custom SavedObjects client. In that case it's up to the plugin to initialize those clients like the example below: ```ts // server/plugin.ts @@ -302,4 +302,4 @@ These saved objects are automatically consumed by the stats API and surfaced und By storing these metrics and their counts as key-value pairs, we can add more metrics without having to worry about exceeding the 1000-field soft limit in Elasticsearch. -The only caveat is that it makes it harder to consume in Kibana when analysing each entry in the array separately. In the telemetry team we are working to find a solution to this. We are building a new way of reporting telemetry called [Pulse](../../../rfcs/text/0008_pulse.md) that will help on making these UI-Metrics easier to consume. +The only caveat is that it makes it harder to consume in Kibana when analysing each entry in the array separately. In the telemetry team we are working to find a solution to this. diff --git a/src/plugins/usage_collection/server/collector/collector.ts b/src/plugins/usage_collection/server/collector/collector.ts index d57700024c088..365e1ce201337 100644 --- a/src/plugins/usage_collection/server/collector/collector.ts +++ b/src/plugins/usage_collection/server/collector/collector.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Logger, LegacyAPICaller } from 'kibana/server'; +import { Logger, LegacyAPICaller, ElasticsearchClient } from 'kibana/server'; export type CollectorFormatForBulkUpload = (result: T) => { type: string; payload: U }; @@ -48,7 +48,7 @@ export interface CollectorOptions { type: string; init?: Function; schema?: MakeSchemaFrom>; // Using Required to enforce all optional keys in the object - fetch: (callCluster: LegacyAPICaller) => Promise | T; + fetch: (callCluster: LegacyAPICaller, esClient?: ElasticsearchClient) => Promise | T; /* * A hook for allowing the fetched data payload to be organized into a typed * data model for internal bulk upload. See defaultFormatterForBulkUpload for diff --git a/src/plugins/usage_collection/server/collector/collector_set.test.ts b/src/plugins/usage_collection/server/collector/collector_set.test.ts index 545642c5dcfa3..3f943ad8bf2ff 100644 --- a/src/plugins/usage_collection/server/collector/collector_set.test.ts +++ b/src/plugins/usage_collection/server/collector/collector_set.test.ts @@ -21,7 +21,7 @@ import { noop } from 'lodash'; import { Collector } from './collector'; import { CollectorSet } from './collector_set'; import { UsageCollector } from './usage_collector'; -import { loggingSystemMock } from '../../../../core/server/mocks'; +import { elasticsearchServiceMock, loggingSystemMock } from '../../../../core/server/mocks'; const logger = loggingSystemMock.createLogger(); @@ -42,6 +42,7 @@ describe('CollectorSet', () => { }); const mockCallCluster = jest.fn().mockResolvedValue({ passTest: 1000 }); + const mockEsClient = elasticsearchServiceMock.createClusterClient().asInternalUser; it('should throw an error if non-Collector type of object is registered', () => { const collectors = new CollectorSet({ logger }); @@ -85,7 +86,7 @@ describe('CollectorSet', () => { }) ); - const result = await collectors.bulkFetch(mockCallCluster); + const result = await collectors.bulkFetch(mockCallCluster, mockEsClient); expect(loggerSpies.debug).toHaveBeenCalledTimes(1); expect(loggerSpies.debug).toHaveBeenCalledWith( 'Fetching data from MY_TEST_COLLECTOR collector' @@ -110,7 +111,7 @@ describe('CollectorSet', () => { let result; try { - result = await collectors.bulkFetch(mockCallCluster); + result = await collectors.bulkFetch(mockCallCluster, mockEsClient); } catch (err) { // Do nothing } @@ -128,7 +129,7 @@ describe('CollectorSet', () => { }) ); - const result = await collectors.bulkFetch(mockCallCluster); + const result = await collectors.bulkFetch(mockCallCluster, mockEsClient); expect(result).toStrictEqual([ { type: 'MY_TEST_COLLECTOR', @@ -146,7 +147,7 @@ describe('CollectorSet', () => { } as any) ); - const result = await collectors.bulkFetch(mockCallCluster); + const result = await collectors.bulkFetch(mockCallCluster, mockEsClient); expect(result).toStrictEqual([ { type: 'MY_TEST_COLLECTOR', @@ -169,7 +170,7 @@ describe('CollectorSet', () => { }) ); - const result = await collectors.bulkFetch(mockCallCluster); + const result = await collectors.bulkFetch(mockCallCluster, mockEsClient); expect(result).toStrictEqual([ { type: 'MY_TEST_COLLECTOR', diff --git a/src/plugins/usage_collection/server/collector/collector_set.ts b/src/plugins/usage_collection/server/collector/collector_set.ts index fce17a46b7168..6861be7f4f76b 100644 --- a/src/plugins/usage_collection/server/collector/collector_set.ts +++ b/src/plugins/usage_collection/server/collector/collector_set.ts @@ -18,7 +18,7 @@ */ import { snakeCase } from 'lodash'; -import { Logger, LegacyAPICaller } from 'kibana/server'; +import { Logger, LegacyAPICaller, ElasticsearchClient } from 'kibana/server'; import { Collector, CollectorOptions } from './collector'; import { UsageCollector } from './usage_collector'; @@ -117,8 +117,12 @@ export class CollectorSet { return allReady; }; + // all collections eventually pass through bulkFetch. + // the shape of the response is different when using the new ES client as is the error handling. + // We'll handle the refactor for using the new client in a follow up PR. public bulkFetch = async ( callCluster: LegacyAPICaller, + esClient: ElasticsearchClient, collectors: Map> = this.collectors ) => { const responses = await Promise.all( @@ -127,7 +131,7 @@ export class CollectorSet { try { return { type: collector.type, - result: await collector.fetch(callCluster), + result: await collector.fetch(callCluster, esClient), // each collector must ensure they handle the response appropriately. }; } catch (err) { this.logger.warn(err); @@ -149,9 +153,9 @@ export class CollectorSet { return this.makeCollectorSetFromArray(filtered); }; - public bulkFetchUsage = async (callCluster: LegacyAPICaller) => { + public bulkFetchUsage = async (callCluster: LegacyAPICaller, esClient: ElasticsearchClient) => { const usageCollectors = this.getFilteredCollectorSet((c) => c instanceof UsageCollector); - return await this.bulkFetch(callCluster, usageCollectors.collectors); + return await this.bulkFetch(callCluster, esClient, usageCollectors.collectors); }; // convert an array of fetched stats results into key/object diff --git a/src/plugins/usage_collection/server/routes/stats.ts b/src/plugins/usage_collection/server/routes/stats.ts index 7c64c9f180319..ef5da2eb11ba6 100644 --- a/src/plugins/usage_collection/server/routes/stats.ts +++ b/src/plugins/usage_collection/server/routes/stats.ts @@ -24,6 +24,7 @@ import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; import { + ElasticsearchClient, IRouter, LegacyAPICaller, MetricsServiceSetup, @@ -61,8 +62,11 @@ export function registerStatsRoute({ metrics: MetricsServiceSetup; overallStatus$: Observable; }) { - const getUsage = async (callCluster: LegacyAPICaller): Promise => { - const usage = await collectorSet.bulkFetchUsage(callCluster); + const getUsage = async ( + callCluster: LegacyAPICaller, + esClient: ElasticsearchClient + ): Promise => { + const usage = await collectorSet.bulkFetchUsage(callCluster, esClient); return collectorSet.toObject(usage); }; @@ -96,13 +100,14 @@ export function registerStatsRoute({ let extended; if (isExtended) { const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; + const esClient = context.core.elasticsearch.client.asCurrentUser; const collectorsReady = await collectorSet.areAllCollectorsReady(); if (shouldGetUsage && !collectorsReady) { return res.customError({ statusCode: 503, body: { message: STATS_NOT_READY_MESSAGE } }); } - const usagePromise = shouldGetUsage ? getUsage(callCluster) : Promise.resolve({}); + const usagePromise = shouldGetUsage ? getUsage(callCluster, esClient) : Promise.resolve({}); const [usage, clusterUuid] = await Promise.all([usagePromise, getClusterUuid(callCluster)]); let modifiedUsage = usage; diff --git a/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx b/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx index ff0cc89a5d9c9..6df205b21d910 100644 --- a/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx +++ b/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx @@ -18,11 +18,14 @@ */ import React from 'react'; -import { render, mount } from 'enzyme'; +import { wait } from '@testing-library/dom'; +import { render, cleanup } from '@testing-library/react/pure'; import { MarkdownVisWrapper } from './markdown_vis_controller'; +afterEach(cleanup); + describe('markdown vis controller', () => { - it('should set html from markdown params', () => { + it('should set html from markdown params', async () => { const vis = { params: { openLinksInNewTab: false, @@ -32,13 +35,22 @@ describe('markdown vis controller', () => { }, }; - const wrapper = render( + const { getByTestId, getByText } = render( ); - expect(wrapper.find('a').text()).toBe('markdown'); + + await wait(() => getByTestId('markdownBody')); + + expect(getByText('markdown')).toMatchInlineSnapshot(` + + markdown + + `); }); - it('should not render the html', () => { + it('should not render the html', async () => { const vis = { params: { openLinksInNewTab: false, @@ -47,13 +59,20 @@ describe('markdown vis controller', () => { }, }; - const wrapper = render( + const { getByTestId, getByText } = render( ); - expect(wrapper.text()).toBe('Testing html\n'); + + await wait(() => getByTestId('markdownBody')); + + expect(getByText(/testing/i)).toMatchInlineSnapshot(` +

+ Testing <a>html</a> +

+ `); }); - it('should update the HTML when render again with changed params', () => { + it('should update the HTML when render again with changed params', async () => { const vis = { params: { openLinksInNewTab: false, @@ -62,13 +81,20 @@ describe('markdown vis controller', () => { }, }; - const wrapper = mount( + const { getByTestId, getByText, rerender } = render( ); - expect(wrapper.text().trim()).toBe('Initial'); + + await wait(() => getByTestId('markdownBody')); + + expect(getByText(/initial/i)).toBeInTheDocument(); + vis.params.markdown = 'Updated'; - wrapper.setProps({ vis }); - expect(wrapper.text().trim()).toBe('Updated'); + rerender( + + ); + + expect(getByText(/Updated/i)).toBeInTheDocument(); }); describe('renderComplete', () => { @@ -86,56 +112,71 @@ describe('markdown vis controller', () => { renderComplete.mockClear(); }); - it('should be called on initial rendering', () => { - mount( + it('should be called on initial rendering', async () => { + const { getByTestId } = render( ); - expect(renderComplete.mock.calls.length).toBe(1); + + await wait(() => getByTestId('markdownBody')); + + expect(renderComplete).toHaveBeenCalledTimes(1); }); - it('should be called on successive render when params change', () => { - mount( + it('should be called on successive render when params change', async () => { + const { getByTestId, rerender } = render( ); - expect(renderComplete.mock.calls.length).toBe(1); + + await wait(() => getByTestId('markdownBody')); + + expect(renderComplete).toHaveBeenCalledTimes(1); + renderComplete.mockClear(); vis.params.markdown = 'changed'; - mount( + + rerender( ); - expect(renderComplete.mock.calls.length).toBe(1); + + expect(renderComplete).toHaveBeenCalledTimes(1); }); - it('should be called on successive render even without data change', () => { - mount( + it('should be called on successive render even without data change', async () => { + const { getByTestId, rerender } = render( ); - expect(renderComplete.mock.calls.length).toBe(1); + + await wait(() => getByTestId('markdownBody')); + + expect(renderComplete).toHaveBeenCalledTimes(1); + renderComplete.mockClear(); - mount( + + rerender( ); - expect(renderComplete.mock.calls.length).toBe(1); + + expect(renderComplete).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js index d55afeda62e70..1341cf02202a0 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js @@ -20,6 +20,7 @@ import _, { isArray, last, get } from 'lodash'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { RedirectAppLinks } from '../../../../../../kibana_react/public'; import { createTickFormatter } from '../../lib/tick_formatter'; import { calculateLabel } from '../../../../../../../plugins/vis_type_timeseries/common/calculate_label'; import { isSortable } from './is_sortable'; @@ -27,7 +28,7 @@ import { EuiToolTip, EuiIcon } from '@elastic/eui'; import { replaceVars } from '../../lib/replace_vars'; import { fieldFormats } from '../../../../../../../plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getFieldFormats } from '../../../../services'; +import { getFieldFormats, getCoreStart } from '../../../../services'; import { METRIC_TYPES } from '../../../../../../../plugins/vis_type_timeseries/common/metric_types'; @@ -231,12 +232,16 @@ export class TableVis extends Component { ); } return ( -
+ {header}{rows}
-
+ ); } } diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js index a4fe6f796bc0b..e9f64c93d337f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js @@ -17,6 +17,7 @@ * under the License. */ +import { getCoreStart } from '../../../../services'; import { createTickFormatter } from '../../lib/tick_formatter'; import { TopN } from '../../../visualizations/views/top_n'; import { getLastValue } from '../../../../../../../plugins/vis_type_timeseries/common/get_last_value'; @@ -89,7 +90,8 @@ export function TopNVisualization(props) { if (model.drilldown_url) { params.onClick = (item) => { - window.location = replaceVars(model.drilldown_url, {}, { key: item.label }); + const url = replaceVars(model.drilldown_url, {}, { key: item.label }); + getCoreStart().application.navigateToUrl(url); }; } diff --git a/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js b/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js index 4883a8129903a..5c7656efe925b 100644 --- a/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js +++ b/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js @@ -162,12 +162,15 @@ describe('VegaParser._resolveEsQueries', () => { function check(spec, expected, warnCount) { return async () => { - const vp = new VegaParser(spec, searchApiStub, 0, 0, { - getFileLayers: async () => [{ name: 'file1', url: 'url1' }], - getUrlForRegionLayer: async (layer) => { - return layer.url; - }, - }); + const mockGetServiceSettings = async () => { + return { + getFileLayers: async () => [{ name: 'file1', url: 'url1' }], + getUrlForRegionLayer: async (layer) => { + return layer.url; + }, + }; + }; + const vp = new VegaParser(spec, searchApiStub, 0, 0, mockGetServiceSettings); await vp._resolveDataUrls(); expect(vp.spec).toEqual(expected); diff --git a/src/plugins/vis_type_vega/public/data_model/vega_parser.ts b/src/plugins/vis_type_vega/public/data_model/vega_parser.ts index b3fb2d54cb266..ccb89207e222f 100644 --- a/src/plugins/vis_type_vega/public/data_model/vega_parser.ts +++ b/src/plugins/vis_type_vega/public/data_model/vega_parser.ts @@ -65,7 +65,7 @@ export class VegaParser { hideWarnings: boolean; error?: string; warnings: string[]; - _urlParsers: UrlParserConfig; + _urlParsers: UrlParserConfig | undefined; isVegaLite?: boolean; useHover?: boolean; _config?: VegaConfig; @@ -80,13 +80,16 @@ export class VegaParser { containerDir?: ControlsLocation | ControlsDirection; controlsDir?: ControlsLocation; searchAPI: SearchAPI; + getServiceSettings: () => Promise; + filters: Bool; + timeCache: TimeCache; constructor( spec: VegaSpec | string, searchAPI: SearchAPI, timeCache: TimeCache, filters: Bool, - serviceSettings: IServiceSettings + getServiceSettings: () => Promise ) { this.spec = spec as VegaSpec; this.hideWarnings = false; @@ -94,13 +97,9 @@ export class VegaParser { this.error = undefined; this.warnings = []; this.searchAPI = searchAPI; - - const onWarn = this._onWarning.bind(this); - this._urlParsers = { - elasticsearch: new EsQueryParser(timeCache, this.searchAPI, filters, onWarn), - emsfile: new EmsFileParser(serviceSettings), - url: new UrlParser(onWarn), - }; + this.getServiceSettings = getServiceSettings; + this.filters = filters; + this.timeCache = timeCache; } async parseAsync() { @@ -123,7 +122,7 @@ export class VegaParser { throw new Error( i18n.translate('visTypeVega.vegaParser.inputSpecDoesNotSpecifySchemaErrorMessage', { defaultMessage: `Your specification requires a {schemaParam} field with a valid URL for -Vega (see {vegaSchemaUrl}) or +Vega (see {vegaSchemaUrl}) or Vega-Lite (see {vegaLiteSchemaUrl}). The URL is an identifier only. Kibana and your browser will never access this URL.`, values: { @@ -547,6 +546,15 @@ The URL is an identifier only. Kibana and your browser will never access this UR * @private */ async _resolveDataUrls() { + if (!this._urlParsers) { + const serviceSettings = await this.getServiceSettings(); + const onWarn = this._onWarning.bind(this); + this._urlParsers = { + elasticsearch: new EsQueryParser(this.timeCache, this.searchAPI, this.filters, onWarn), + emsfile: new EmsFileParser(serviceSettings), + url: new UrlParser(onWarn), + }; + } const pending: PendingType = {}; this.searchAPI.resetSearchStats(); @@ -560,7 +568,7 @@ The URL is an identifier only. Kibana and your browser will never access this UR type = DEFAULT_PARSER; } - const parser = this._urlParsers[type]; + const parser = this._urlParsers![type]; if (parser === undefined) { throw new Error( i18n.translate('visTypeVega.vegaParser.notSupportedUrlTypeErrorMessage', { @@ -584,7 +592,7 @@ The URL is an identifier only. Kibana and your browser will never access this UR if (pendingParsers.length > 0) { // let each parser populate its data in parallel await Promise.all( - pendingParsers.map((type) => this._urlParsers[type].populateData(pending[type])) + pendingParsers.map((type) => this._urlParsers![type].populateData(pending[type])) ); } } diff --git a/src/plugins/vis_type_vega/public/plugin.ts b/src/plugins/vis_type_vega/public/plugin.ts index 4b8ff8e2cb43a..ce5c5130961c6 100644 --- a/src/plugins/vis_type_vega/public/plugin.ts +++ b/src/plugins/vis_type_vega/public/plugin.ts @@ -28,7 +28,6 @@ import { setSavedObjects, setInjectedVars, setUISettings, - setKibanaMapFactory, setMapsLegacyConfig, setInjectedMetadata, } from './services'; @@ -47,7 +46,7 @@ export interface VegaVisualizationDependencies { plugins: { data: DataPublicPluginSetup; }; - serviceSettings: IServiceSettings; + getServiceSettings: () => Promise; } /** @internal */ @@ -81,7 +80,6 @@ export class VegaPlugin implements Plugin, void> { emsTileLayerId: core.injectedMetadata.getInjectedVar('emsTileLayerId', true), }); setUISettings(core.uiSettings); - setKibanaMapFactory(mapsLegacy.getKibanaMapFactoryProvider); setMapsLegacyConfig(mapsLegacy.config); const visualizationDependencies: Readonly = { @@ -89,7 +87,7 @@ export class VegaPlugin implements Plugin, void> { plugins: { data, }, - serviceSettings: mapsLegacy.serviceSettings, + getServiceSettings: mapsLegacy.getServiceSettings, }; inspector.registerView(getVegaInspectorView({ uiSettings: core.uiSettings })); diff --git a/src/plugins/vis_type_vega/public/services.ts b/src/plugins/vis_type_vega/public/services.ts index dfb2c96e9f894..455fe67dbc423 100644 --- a/src/plugins/vis_type_vega/public/services.ts +++ b/src/plugins/vis_type_vega/public/services.ts @@ -33,9 +33,6 @@ export const [getData, setData] = createGetterSetter('Dat export const [getNotifications, setNotifications] = createGetterSetter( 'Notifications' ); -export const [getKibanaMapFactory, setKibanaMapFactory] = createGetterSetter( - 'KibanaMapFactory' -); export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); diff --git a/src/plugins/vis_type_vega/public/vega_request_handler.ts b/src/plugins/vis_type_vega/public/vega_request_handler.ts index c09a9466df602..f48b61ed70822 100644 --- a/src/plugins/vis_type_vega/public/vega_request_handler.ts +++ b/src/plugins/vis_type_vega/public/vega_request_handler.ts @@ -40,7 +40,7 @@ interface VegaRequestHandlerContext { } export function createVegaRequestHandler( - { plugins: { data }, core: { uiSettings }, serviceSettings }: VegaVisualizationDependencies, + { plugins: { data }, core: { uiSettings }, getServiceSettings }: VegaVisualizationDependencies, context: VegaRequestHandlerContext = {} ) { let searchAPI: SearchAPI; @@ -70,7 +70,7 @@ export function createVegaRequestHandler( const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); const filtersDsl = esQuery.buildEsQuery(undefined, query, filters, esQueryConfigs); const { VegaParser } = await import('./data_model/vega_parser'); - const vp = new VegaParser(visParams.spec, searchAPI, timeCache, filtersDsl, serviceSettings); + const vp = new VegaParser(visParams.spec, searchAPI, timeCache, filtersDsl, getServiceSettings); return await vp.parseAsync(); }; diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js b/src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js index bc1cb4e4734c7..ec4959a51d741 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js @@ -17,16 +17,16 @@ * under the License. */ -import { KibanaMapLayer, L } from '../../../maps_legacy/public'; +import { KibanaMapLayer } from '../../../maps_legacy/public'; export class VegaMapLayer extends KibanaMapLayer { - constructor(spec, options) { + constructor(spec, options, leaflet) { super(); // Used by super.getAttributions() this._attribution = options.attribution; delete options.attribution; - this._leafletLayer = L.vega(spec, options); + this._leafletLayer = leaflet.vega(spec, options); } getVegaView() { diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js index 78ae2efdbdda5..d925aaeeced66 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js @@ -21,7 +21,8 @@ import { i18n } from '@kbn/i18n'; import { vega } from '../lib/vega'; import { VegaBaseView } from './vega_base_view'; import { VegaMapLayer } from './vega_map_layer'; -import { getEmsTileLayerId, getUISettings, getKibanaMapFactory } from '../services'; +import { getEmsTileLayerId, getUISettings } from '../services'; +import { lazyLoadMapsLegacyModules } from '../../../maps_legacy/public'; export class VegaMapView extends VegaBaseView { constructor(opts) { @@ -106,7 +107,9 @@ export class VegaMapView extends VegaBaseView { // maxBounds = L.latLngBounds(L.latLng(b[1], b[0]), L.latLng(b[3], b[2])); // } - this._kibanaMap = getKibanaMapFactory()(this._$container.get(0), { + const modules = await lazyLoadMapsLegacyModules(); + + this._kibanaMap = new modules.KibanaMap(this._$container.get(0), { zoom, minZoom, maxZoom, @@ -122,14 +125,18 @@ export class VegaMapView extends VegaBaseView { }); } - const vegaMapLayer = new VegaMapLayer(this._parser.spec, { - vega, - bindingsContainer: this._$controls.get(0), - delayRepaint: mapConfig.delayRepaint, - viewConfig: this._vegaViewConfig, - onWarning: this.onWarn.bind(this), - onError: this.onError.bind(this), - }); + const vegaMapLayer = new VegaMapLayer( + this._parser.spec, + { + vega, + bindingsContainer: this._$controls.get(0), + delayRepaint: mapConfig.delayRepaint, + viewConfig: this._vegaViewConfig, + onWarning: this.onWarn.bind(this), + onError: this.onError.bind(this), + }, + modules.L + ); this._kibanaMap.addLayer(vegaMapLayer); diff --git a/src/plugins/vis_type_vega/public/vega_visualization.js b/src/plugins/vis_type_vega/public/vega_visualization.js index d6db0f9ea239f..2d58e9cda60cd 100644 --- a/src/plugins/vis_type_vega/public/vega_visualization.js +++ b/src/plugins/vis_type_vega/public/vega_visualization.js @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { getNotifications, getData, getSavedObjects } from './services'; -export const createVegaVisualization = ({ serviceSettings }) => +export const createVegaVisualization = ({ getServiceSettings }) => class VegaVisualization { constructor(el, vis) { this._el = el; @@ -102,6 +102,7 @@ export const createVegaVisualization = ({ serviceSettings }) => this._vegaView = null; } + const serviceSettings = await getServiceSettings(); const { filterManager } = this.dataPlugin.query; const { timefilter } = this.dataPlugin.query.timefilter; const vegaViewParams = { diff --git a/src/plugins/vis_type_vega/public/vega_visualization.test.js b/src/plugins/vis_type_vega/public/vega_visualization.test.js index 1bf625af76207..dcf1722768075 100644 --- a/src/plugins/vis_type_vega/public/vega_visualization.test.js +++ b/src/plugins/vis_type_vega/public/vega_visualization.test.js @@ -32,16 +32,9 @@ import { SearchAPI } from './data_model/search_api'; import { createVegaTypeDefinition } from './vega_type'; -import { - setInjectedVars, - setData, - setSavedObjects, - setNotifications, - setKibanaMapFactory, -} from './services'; +import { setInjectedVars, setData, setSavedObjects, setNotifications } from './services'; import { coreMock } from '../../../core/public/mocks'; import { dataPluginMock } from '../../data/public/mocks'; -import { KibanaMap } from '../../maps_legacy/public/map/kibana_map'; jest.mock('./default_spec', () => ({ getDefaultSpec: () => jest.requireActual('./test_utils/default.spec.json'), @@ -77,8 +70,11 @@ describe('VegaVisualizations', () => { mockHeight = jest.spyOn($.prototype, 'height').mockImplementation(() => mockedHeightValue); }; + const mockGetServiceSettings = async () => { + return {}; + }; + beforeEach(() => { - setKibanaMapFactory((...args) => new KibanaMap(...args)); setInjectedVars({ emsTileLayerId: {}, enableExternalUrls: true, @@ -92,6 +88,7 @@ describe('VegaVisualizations', () => { plugins: { data: dataPluginMock.createSetupContract(), }, + getServiceSettings: mockGetServiceSettings, }; vegaVisType = createVegaTypeDefinition(vegaVisualizationDependencies); @@ -128,7 +125,10 @@ describe('VegaVisualizations', () => { search: dataPluginStart.search, uiSettings: coreStart.uiSettings, injectedMetadata: coreStart.injectedMetadata, - }) + }), + 0, + 0, + mockGetServiceSettings ); await vegaParser.parseAsync(); await vegaVis.render(vegaParser); @@ -155,7 +155,10 @@ describe('VegaVisualizations', () => { search: dataPluginStart.search, uiSettings: coreStart.uiSettings, injectedMetadata: coreStart.injectedMetadata, - }) + }), + 0, + 0, + mockGetServiceSettings ); await vegaParser.parseAsync(); @@ -176,7 +179,10 @@ describe('VegaVisualizations', () => { search: dataPluginStart.search, uiSettings: coreStart.uiSettings, injectedMetadata: coreStart.injectedMetadata, - }) + }), + 0, + 0, + mockGetServiceSettings ); await vegaParser.parseAsync(); diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index 18ae68ec40fe5..c091d396b4924 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -243,12 +243,6 @@ export class VisualizeEmbeddable dirty = true; } - // propagate the title to the output embeddable - // but only when the visualization is in edit/Visualize mode - if (!this.parent && this.vis.title !== this.output.title) { - this.updateOutput({ title: this.vis.title }); - } - if (this.vis.description && this.domNode) { this.domNode.setAttribute('data-description', this.vis.description); } diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index 1d01900ceffc2..a9bf6bd171f15 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -174,7 +174,9 @@ class NewVisModal extends React.Component void; originatingApp?: string; outsideVisualizeApp?: boolean; + createByValue?: boolean; } /** diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx index 0423e48bfb41e..12720f3f22e7c 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx @@ -21,8 +21,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { TopNavMenuData } from 'src/plugins/navigation/public'; -import uuid from 'uuid'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../visualizations/public'; +import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeInput } from '../../../../visualizations/public'; import { showSaveModal, SavedObjectSaveModalOrigin, @@ -122,7 +121,7 @@ export const getTopNavConfig = ( if (newlyCreated && stateTransfer) { stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { - state: { id, type: VISUALIZE_EMBEDDABLE_TYPE }, + state: { type: VISUALIZE_EMBEDDABLE_TYPE, input: { savedObjectId: id } }, }); } else { application.navigateToApp(originatingApp); @@ -167,15 +166,11 @@ export const getTopNavConfig = ( } const state = { input: { - ...vis.serialize(), - id: embeddableId ? embeddableId : uuid.v4(), - }, + savedVis: vis.serialize(), + } as VisualizeInput, + embeddableId, type: VISUALIZE_EMBEDDABLE_TYPE, - embeddableId: '', }; - if (embeddableId) { - state.embeddableId = embeddableId; - } stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { state }); }; @@ -267,6 +262,7 @@ export const getTopNavConfig = ( } const currentTitle = savedVis.title; savedVis.title = newTitle; + embeddableHandler.updateInput({ title: newTitle }); savedVis.copyOnSave = newCopyOnSave; savedVis.description = newDescription; const saveOptions = { @@ -282,6 +278,7 @@ export const getTopNavConfig = ( } return response; }; + const saveModal = ( { await supertest - .post('/api/kibana/kql_opt_in_telemetry') + .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') .send({ opt_in: true }) .expect(200); @@ -50,7 +50,7 @@ export default function ({ getService }) { it('should increment the opt *out* counter in the .kibana/kql-telemetry document', async () => { await supertest - .post('/api/kibana/kql_opt_in_telemetry') + .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') .send({ opt_in: false }) .expect(200); @@ -68,7 +68,7 @@ export default function ({ getService }) { it('should report success when opt *in* is incremented successfully', () => { return supertest - .post('/api/kibana/kql_opt_in_telemetry') + .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') .send({ opt_in: true }) .expect('Content-Type', /json/) @@ -80,7 +80,7 @@ export default function ({ getService }) { it('should report success when opt *out* is incremented successfully', () => { return supertest - .post('/api/kibana/kql_opt_in_telemetry') + .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') .send({ opt_in: false }) .expect('Content-Type', /json/) @@ -93,27 +93,27 @@ export default function ({ getService }) { it('should only accept literal boolean values for the opt_in POST body param', function () { return Bluebird.all([ supertest - .post('/api/kibana/kql_opt_in_telemetry') + .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') .send({ opt_in: 'notabool' }) .expect(400), supertest - .post('/api/kibana/kql_opt_in_telemetry') + .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') .send({ opt_in: 0 }) .expect(400), supertest - .post('/api/kibana/kql_opt_in_telemetry') + .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') .send({ opt_in: null }) .expect(400), supertest - .post('/api/kibana/kql_opt_in_telemetry') + .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') .send({ opt_in: undefined }) .expect(400), supertest - .post('/api/kibana/kql_opt_in_telemetry') + .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') .send({}) .expect(400), diff --git a/test/functional/apps/discover/_doc_navigation.js b/test/functional/apps/discover/_doc_navigation.js index 5ae799f8756c0..31aef96918ffa 100644 --- a/test/functional/apps/discover/_doc_navigation.js +++ b/test/functional/apps/discover/_doc_navigation.js @@ -28,8 +28,8 @@ export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const retry = getService('retry'); - // Flaky: https://github.com/elastic/kibana/issues/71216 - describe('doc link in discover', function contextSize() { + // FLAKY: https://github.com/elastic/kibana/issues/78373 + describe.skip('doc link in discover', function contextSize() { beforeEach(async function () { log.debug('load kibana index with default index pattern'); await esArchiver.loadIfNeeded('discover'); diff --git a/test/functional/apps/discover/_inspector.js b/test/functional/apps/discover/_inspector.js index 900ad28e14e69..fcb66fbd52cf7 100644 --- a/test/functional/apps/discover/_inspector.js +++ b/test/functional/apps/discover/_inspector.js @@ -34,7 +34,8 @@ export default function ({ getService, getPageObjects }) { return hitsCountStatsRow[STATS_ROW_VALUE_INDEX]; } - describe('inspect', () => { + // FLAKY: https://github.com/elastic/kibana/issues/39842 + describe.skip('inspect', () => { before(async () => { await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.load('discover'); diff --git a/test/functional/apps/management/_index_pattern_popularity.js b/test/functional/apps/management/_index_pattern_popularity.js index e2fcf50ef2c12..530b8e1111a0c 100644 --- a/test/functional/apps/management/_index_pattern_popularity.js +++ b/test/functional/apps/management/_index_pattern_popularity.js @@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }) { // check that it is 0 (previous increase was cancelled const popularity = await PageObjects.settings.getPopularity(); log.debug('popularity = ' + popularity); - expect(popularity).to.be(''); + expect(popularity).to.be('0'); }); it('can be saved', async function () { diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index daf1659f0cfe1..f5fb54c72177f 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -474,6 +474,10 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { return parseInt(scrollSize, 10); } + public async scrollTop() { + await driver.executeScript('document.documentElement.scrollTop = 0'); + } + // return promise with REAL scroll position public async setScrollTop(scrollSize: number | string) { await driver.executeScript('document.body.scrollTop = ' + scrollSize); diff --git a/test/plugin_functional/plugins/elasticsearch_client_plugin/server/plugin.ts b/test/plugin_functional/plugins/elasticsearch_client_plugin/server/plugin.ts index 5e018ca7818d3..8b6c8a99c73e8 100644 --- a/test/plugin_functional/plugins/elasticsearch_client_plugin/server/plugin.ts +++ b/test/plugin_functional/plugins/elasticsearch_client_plugin/server/plugin.ts @@ -26,7 +26,7 @@ export class ElasticsearchClientPlugin implements Plugin { { path: '/api/elasticsearch_client_plugin/context/ping', validate: false }, async (context, req, res) => { const { body } = await context.core.elasticsearch.client.asInternalUser.ping(); - return res.ok({ body }); + return res.ok({ body: JSON.stringify(body) }); } ); router.get( @@ -34,14 +34,14 @@ export class ElasticsearchClientPlugin implements Plugin { async (context, req, res) => { const [coreStart] = await core.getStartServices(); const { body } = await coreStart.elasticsearch.client.asInternalUser.ping(); - return res.ok({ body }); + return res.ok({ body: JSON.stringify(body) }); } ); router.get( { path: '/api/elasticsearch_client_plugin/custom_client/ping', validate: false }, async (context, req, res) => { const { body } = await this.client!.asInternalUser.ping(); - return res.ok({ body }); + return res.ok({ body: JSON.stringify(body) }); } ); } diff --git a/test/plugin_functional/plugins/index_patterns/server/plugin.ts b/test/plugin_functional/plugins/index_patterns/server/plugin.ts index 1c85f226623cb..ddf9acb259983 100644 --- a/test/plugin_functional/plugins/index_patterns/server/plugin.ts +++ b/test/plugin_functional/plugins/index_patterns/server/plugin.ts @@ -78,7 +78,7 @@ export class IndexPatternsTestPlugin const id = (req.params as Record).id; const service = await data.indexPatterns.indexPatternsServiceFactory(req); const ip = await service.get(id); - await service.save(ip); + await service.updateSavedObject(ip); return res.ok(); } ); diff --git a/test/plugin_functional/test_suites/data_plugin/index_patterns.ts b/test/plugin_functional/test_suites/data_plugin/index_patterns.ts index 2db9eb733f805..7e736ea7a066f 100644 --- a/test/plugin_functional/test_suites/data_plugin/index_patterns.ts +++ b/test/plugin_functional/test_suites/data_plugin/index_patterns.ts @@ -46,14 +46,14 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const body = await ( await supertest.get(`/api/index-patterns-plugin/get/${indexPatternId}`).expect(200) ).body; - expect(body.fields.length > 0).to.equal(true); + expect(typeof body.id).to.equal('string'); }); it('can update index pattern', async () => { - const body = await ( - await supertest.get(`/api/index-patterns-plugin/update/${indexPatternId}`).expect(200) - ).body; - expect(body).to.eql({}); + const resp = await supertest + .get(`/api/index-patterns-plugin/update/${indexPatternId}`) + .expect(200); + expect(resp.body).to.eql({}); }); it('can delete index pattern', async () => { diff --git a/test/typings/query_string.d.ts b/test/typings/query_string.d.ts deleted file mode 100644 index 3e4a8fa4da6a0..0000000000000 --- a/test/typings/query_string.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -declare module 'query-string' { - type ArrayFormat = 'bracket' | 'index' | 'none'; - - export interface ParseOptions { - arrayFormat?: ArrayFormat; - sort: ((itemLeft: string, itemRight: string) => number) | false; - } - - export interface ParsedQuery { - [key: string]: T | T[] | null | undefined; - } - - export function parse(str: string, options?: ParseOptions): ParsedQuery; - - export function parseUrl(str: string, options?: ParseOptions): { url: string; query: any }; - - export interface StringifyOptions { - strict?: boolean; - encode?: boolean; - arrayFormat?: ArrayFormat; - sort: ((itemLeft: string, itemRight: string) => number) | false; - } - - export function stringify(obj: object, options?: StringifyOptions): string; - - export function extract(str: string): string; -} diff --git a/typings/query_string.d.ts b/typings/query_string.d.ts deleted file mode 100644 index 3e4a8fa4da6a0..0000000000000 --- a/typings/query_string.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -declare module 'query-string' { - type ArrayFormat = 'bracket' | 'index' | 'none'; - - export interface ParseOptions { - arrayFormat?: ArrayFormat; - sort: ((itemLeft: string, itemRight: string) => number) | false; - } - - export interface ParsedQuery { - [key: string]: T | T[] | null | undefined; - } - - export function parse(str: string, options?: ParseOptions): ParsedQuery; - - export function parseUrl(str: string, options?: ParseOptions): { url: string; query: any }; - - export interface StringifyOptions { - strict?: boolean; - encode?: boolean; - arrayFormat?: ArrayFormat; - sort: ((itemLeft: string, itemRight: string) => number) | false; - } - - export function stringify(obj: object, options?: StringifyOptions): string; - - export function extract(str: string): string; -} diff --git a/x-pack/.gitignore b/x-pack/.gitignore index d73b6f64f036a..99e33dbb88e92 100644 --- a/x-pack/.gitignore +++ b/x-pack/.gitignore @@ -6,13 +6,9 @@ /test/functional/apps/reporting/reports/session /test/reporting/configs/failure_debug/ /plugins/reporting/.chromium/ -/legacy/plugins/reporting/.chromium/ -/legacy/plugins/reporting/.phantom/ /plugins/reporting/chromium/ /plugins/reporting/.phantom/ /.aws-config.json /.env /.kibana-plugin-helpers.dev.* -!/legacy/plugins/infra/**/target .cache -!/legacy/plugins/security_solution/**/target diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index a700781438706..66ae478b86828 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -9,8 +9,8 @@ "xpack.alerts": "plugins/alerts", "xpack.eventLog": "plugins/event_log", "xpack.alertingBuiltins": "plugins/alerting_builtins", - "xpack.apm": ["legacy/plugins/apm", "plugins/apm"], - "xpack.beatsManagement": ["legacy/plugins/beats_management", "plugins/beats_management"], + "xpack.apm": "plugins/apm", + "xpack.beatsManagement": "plugins/beats_management", "xpack.canvas": "plugins/canvas", "xpack.cloud": "plugins/cloud", "xpack.dashboard": "plugins/dashboard_enhanced", @@ -35,15 +35,15 @@ "xpack.lens": "plugins/lens", "xpack.licenseMgmt": "plugins/license_management", "xpack.licensing": "plugins/licensing", - "xpack.logstash": ["plugins/logstash", "legacy/plugins/logstash"], + "xpack.logstash": ["plugins/logstash"], "xpack.main": "legacy/plugins/xpack_main", - "xpack.maps": ["plugins/maps", "legacy/plugins/maps"], - "xpack.ml": ["plugins/ml", "legacy/plugins/ml"], + "xpack.maps": ["plugins/maps"], + "xpack.ml": ["plugins/ml"], "xpack.monitoring": ["plugins/monitoring"], "xpack.remoteClusters": "plugins/remote_clusters", "xpack.painlessLab": "plugins/painless_lab", "xpack.reporting": ["plugins/reporting"], - "xpack.rollupJobs": ["legacy/plugins/rollup", "plugins/rollup"], + "xpack.rollupJobs": ["plugins/rollup"], "xpack.searchProfiler": "plugins/searchprofiler", "xpack.security": "plugins/security", "xpack.server": "legacy/server", @@ -55,6 +55,7 @@ "xpack.triggersActionsUI": "plugins/triggers_actions_ui", "xpack.upgradeAssistant": "plugins/upgrade_assistant", "xpack.uptime": ["plugins/uptime"], + "xpack.urlDrilldown": "plugins/drilldowns/url_drilldown", "xpack.watcher": "plugins/watcher", "xpack.observability": "plugins/observability" }, diff --git a/x-pack/.telemetryrc.json b/x-pack/.telemetryrc.json index 2c16491c1096b..30b2178259d68 100644 --- a/x-pack/.telemetryrc.json +++ b/x-pack/.telemetryrc.json @@ -7,7 +7,6 @@ "plugins/apm/server/lib/apm_telemetry/index.ts", "plugins/canvas/server/collectors/collector.ts", "plugins/infra/server/usage/usage_collector.ts", - "plugins/lens/server/usage/collectors.ts", "plugins/reporting/server/usage/reporting_usage_collector.ts", "plugins/maps/server/maps_telemetry/collectors/register.ts" ] diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index e6f160ce8c654..eec7b0246d026 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -8,17 +8,15 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector const fileMockPath = `${kibanaDirectory}/src/dev/jest/mocks/file_mock.js`; return { rootDir, - roots: ['/plugins', '/legacy/plugins', '/legacy/server'], + roots: ['/plugins'], moduleFileExtensions: ['js', 'mjs', 'json', 'ts', 'tsx', 'node'], moduleNameMapper: { '@elastic/eui$': `${kibanaDirectory}/node_modules/@elastic/eui/test-env`, '@elastic/eui/lib/(.*)?': `${kibanaDirectory}/node_modules/@elastic/eui/test-env/$1`, '^fixtures/(.*)': `${kibanaDirectory}/src/fixtures/$1`, - 'uiExports/(.*)': fileMockPath, '^src/core/(.*)': `${kibanaDirectory}/src/core/$1`, '^src/legacy/(.*)': `${kibanaDirectory}/src/legacy/$1`, '^src/plugins/(.*)': `${kibanaDirectory}/src/plugins/$1`, - '^legacy/plugins/xpack_main/(.*);': `${xPackKibanaDirectory}/legacy/plugins/xpack_main/public/$1`, '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': fileMockPath, '\\.module.(css|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/css_module_mock.js`, '\\.(css|less|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`, @@ -30,8 +28,6 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector '^(!!)?file-loader!': fileMockPath, }, collectCoverageFrom: [ - 'legacy/plugins/**/*.{js,mjs,jsx,ts,tsx}', - 'legacy/server/**/*.{js,mjs,jsx,ts,tsx}', 'plugins/**/*.{js,mjs,jsx,ts,tsx}', '!**/{__test__,__snapshots__,__examples__,integration_tests,tests}/**', '!**/*.test.{js,mjs,ts,tsx}', diff --git a/x-pack/legacy/common/__tests__/poller.js b/x-pack/legacy/common/__tests__/poller.js deleted file mode 100644 index 24558502a8d02..0000000000000 --- a/x-pack/legacy/common/__tests__/poller.js +++ /dev/null @@ -1,240 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { Poller } from '../poller'; - -describe('Poller', () => { - const pollFrequencyInMillis = 20; - let functionToPoll; - let successFunction; - let errorFunction; - let poller; - let clock; - - beforeEach(() => { - clock = sinon.useFakeTimers(); - }); - - afterEach(() => { - clock.restore(); - if (poller) { - poller.stop(); - } - }); - - // Allowing the Poller to poll requires intimate knowledge of the inner workings of the Poller. - // We have to ensure that the Promises internal to the `_poll` method are resolved to queue up - // the next setTimeout before incrementing the clock. The order of this differs slightly when the - // `trailing` is set, hence the different `allowPoll` and `allowDelayPoll` functions. - const queueNextPoll = async () => { - await Promise.resolve(); - await Promise.resolve(); - }; - - const allowPoll = async (interval) => { - await queueNextPoll(); - clock.tick(interval); - }; - - const allowDelayPoll = async (interval) => { - clock.tick(interval); - await queueNextPoll(); - }; - - describe('start()', () => { - beforeEach(() => { - functionToPoll = sinon.spy(() => { - return Promise.resolve(42); - }); - successFunction = sinon.spy(); - errorFunction = sinon.spy(); - poller = new Poller({ - functionToPoll, - successFunction, - errorFunction, - pollFrequencyInMillis, - }); - }); - - describe(`when trailing isn't set`, () => { - it(`polls immediately`, () => { - poller.start(); - expect(functionToPoll.callCount).to.be(1); - }); - }); - - describe(`when trailing is set to true`, () => { - beforeEach(() => { - poller = new Poller({ - functionToPoll, - successFunction, - errorFunction, - pollFrequencyInMillis, - trailing: true, - }); - }); - - it('waits for pollFrequencyInMillis before polling', async () => { - poller.start(); - expect(functionToPoll.callCount).to.be(0); - allowDelayPoll(pollFrequencyInMillis); - expect(functionToPoll.callCount).to.be(1); - }); - }); - - it('polls the functionToPoll multiple times', async () => { - poller.start(); - await allowPoll(pollFrequencyInMillis * 2); - expect(functionToPoll.callCount).to.be.greaterThan(1); - }); - - describe('when the function to poll succeeds', () => { - it('calls the successFunction multiple times', async () => { - poller.start(); - await allowPoll(pollFrequencyInMillis * 2); - expect(successFunction.callCount).to.be.greaterThan(1); - expect(errorFunction.callCount).to.be(0); - }); - }); - - describe('when the function to poll fails', () => { - beforeEach(() => { - functionToPoll = sinon.spy(() => { - return Promise.reject(42); - }); - }); - - describe('when the continuePollingOnError option has not been set', () => { - beforeEach(() => { - poller = new Poller({ - functionToPoll, - successFunction, - errorFunction, - pollFrequencyInMillis, - }); - }); - - it('calls the errorFunction exactly once and polling is stopped', async () => { - poller.start(); - await allowPoll(pollFrequencyInMillis * 4); - expect(poller.isRunning()).to.be(false); - expect(successFunction.callCount).to.be(0); - expect(errorFunction.callCount).to.be(1); - }); - }); - - describe('when the continuePollingOnError option has been set to true', () => { - beforeEach(() => { - poller = new Poller({ - functionToPoll, - successFunction, - errorFunction, - pollFrequencyInMillis, - continuePollingOnError: true, - }); - }); - - it('calls the errorFunction multiple times', async () => { - poller.start(); - await allowPoll(pollFrequencyInMillis); - await allowPoll(pollFrequencyInMillis); - expect(successFunction.callCount).to.be(0); - expect(errorFunction.callCount).to.be.greaterThan(1); - }); - - describe('when pollFrequencyErrorMultiplier has been set', () => { - beforeEach(() => { - poller = new Poller({ - functionToPoll, - successFunction, - errorFunction, - pollFrequencyInMillis, - continuePollingOnError: true, - pollFrequencyErrorMultiplier: 2, - }); - }); - - it('waits for the multiplier * the pollFrequency', async () => { - poller.start(); - await queueNextPoll(); - expect(functionToPoll.callCount).to.be(1); - await allowPoll(pollFrequencyInMillis); - expect(functionToPoll.callCount).to.be(1); - await allowPoll(pollFrequencyInMillis); - expect(functionToPoll.callCount).to.be(2); - }); - }); - }); - }); - }); - - describe('isRunning()', () => { - beforeEach(() => { - functionToPoll = sinon.spy(() => { - return Promise.resolve(42); - }); - poller = new Poller({ - functionToPoll, - }); - }); - - it('returns true immediately after invoking start()', () => { - poller.start(); - expect(poller.isRunning()).to.be(true); - }); - - it('returns false after invoking stop', () => { - poller.start(); - poller.stop(); - expect(poller.isRunning()).to.be(false); - }); - }); - - describe('stop()', () => { - describe(`when successFunction isn't set`, () => { - beforeEach(() => { - functionToPoll = sinon.spy(() => { - return Promise.resolve(42); - }); - poller = new Poller({ - functionToPoll, - pollFrequencyInMillis, - }); - }); - - it(`doesn't poll again`, async () => { - poller.start(); - expect(functionToPoll.callCount).to.be(1); - poller.stop(); - await allowPoll(pollFrequencyInMillis); - expect(functionToPoll.callCount).to.be(1); - }); - }); - - describe(`when successFunction is a Promise`, () => { - beforeEach(() => { - functionToPoll = sinon.spy(() => { - return Promise.resolve(42); - }); - poller = new Poller({ - functionToPoll, - successFunction: Promise.resolve(), - pollFrequencyInMillis, - }); - }); - - it(`doesn't poll again when successFunction is a Promise`, async () => { - poller.start(); - expect(functionToPoll.callCount).to.be(1); - poller.stop(); - await allowPoll(pollFrequencyInMillis); - expect(functionToPoll.callCount).to.be(1); - }); - }); - }); -}); diff --git a/x-pack/legacy/common/constants/index.ts b/x-pack/legacy/common/constants/index.ts deleted file mode 100644 index 4db0f994fd47e..0000000000000 --- a/x-pack/legacy/common/constants/index.ts +++ /dev/null @@ -1,23 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { - LICENSE_STATUS_UNAVAILABLE, - LICENSE_STATUS_INVALID, - LICENSE_STATUS_EXPIRED, - LICENSE_STATUS_VALID, -} from './license_status'; - -export { - LICENSE_TYPE_BASIC, - LICENSE_TYPE_STANDARD, - LICENSE_TYPE_GOLD, - LICENSE_TYPE_PLATINUM, - LICENSE_TYPE_ENTERPRISE, - LICENSE_TYPE_TRIAL, - RANKED_LICENSE_TYPES, - LicenseType, -} from './license_types'; diff --git a/x-pack/legacy/common/constants/license_status.ts b/x-pack/legacy/common/constants/license_status.ts deleted file mode 100644 index 5fdfa08d73959..0000000000000 --- a/x-pack/legacy/common/constants/license_status.ts +++ /dev/null @@ -1,10 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const LICENSE_STATUS_UNAVAILABLE = 'UNAVAILABLE'; -export const LICENSE_STATUS_INVALID = 'INVALID'; -export const LICENSE_STATUS_EXPIRED = 'EXPIRED'; -export const LICENSE_STATUS_VALID = 'VALID'; diff --git a/x-pack/legacy/common/constants/license_types.ts b/x-pack/legacy/common/constants/license_types.ts deleted file mode 100644 index 8c329df2f85f7..0000000000000 --- a/x-pack/legacy/common/constants/license_types.ts +++ /dev/null @@ -1,31 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const LICENSE_TYPE_BASIC = 'basic'; -export const LICENSE_TYPE_STANDARD = 'standard'; -export const LICENSE_TYPE_GOLD = 'gold'; -export const LICENSE_TYPE_PLATINUM = 'platinum'; -export const LICENSE_TYPE_ENTERPRISE = 'enterprise'; -export const LICENSE_TYPE_TRIAL = 'trial'; - -export type LicenseType = - | typeof LICENSE_TYPE_BASIC - | typeof LICENSE_TYPE_STANDARD - | typeof LICENSE_TYPE_GOLD - | typeof LICENSE_TYPE_PLATINUM - | typeof LICENSE_TYPE_ENTERPRISE - | typeof LICENSE_TYPE_TRIAL; - -// These are ordered from least featureful to most featureful, so we can assume that someone holding -// a license at a particular index cannot access any features unlocked by the licenses that follow it. -export const RANKED_LICENSE_TYPES = [ - LICENSE_TYPE_BASIC, - LICENSE_TYPE_STANDARD, - LICENSE_TYPE_GOLD, - LICENSE_TYPE_PLATINUM, - LICENSE_TYPE_ENTERPRISE, - LICENSE_TYPE_TRIAL, -]; diff --git a/x-pack/legacy/common/eui_draggable/index.d.ts b/x-pack/legacy/common/eui_draggable/index.d.ts deleted file mode 100644 index 322966b3c982e..0000000000000 --- a/x-pack/legacy/common/eui_draggable/index.d.ts +++ /dev/null @@ -1,17 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { EuiDraggable, EuiDragDropContext } from '@elastic/eui'; - -type PropsOf = T extends React.ComponentType ? ComponentProps : never; -type FirstArgumentOf = Func extends (arg1: infer FirstArgument, ...rest: any[]) => any - ? FirstArgument - : never; -export type DragHandleProps = FirstArgumentOf< - Exclude['children'], React.ReactElement> ->['dragHandleProps']; -export type DropResult = FirstArgumentOf['onDragEnd']>; diff --git a/x-pack/legacy/common/eui_styled_components/index.ts b/x-pack/legacy/common/eui_styled_components/index.ts deleted file mode 100644 index 9b3ed903627b4..0000000000000 --- a/x-pack/legacy/common/eui_styled_components/index.ts +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - css, - euiStyled, - EuiTheme, - EuiThemeProvider, - createGlobalStyle, - keyframes, - withTheme, -} from './eui_styled_components'; - -export { css, euiStyled, EuiTheme, EuiThemeProvider, createGlobalStyle, keyframes, withTheme }; -// In order to to mimic the styled-components module we need to ignore the following -// eslint-disable-next-line import/no-default-export -export default euiStyled; diff --git a/x-pack/legacy/common/poller.js b/x-pack/legacy/common/poller.js deleted file mode 100644 index 09824ce9d6d23..0000000000000 --- a/x-pack/legacy/common/poller.js +++ /dev/null @@ -1,79 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; - -export class Poller { - constructor(options) { - this.functionToPoll = options.functionToPoll; // Must return a Promise - this.successFunction = options.successFunction || _.noop; - this.errorFunction = options.errorFunction || _.noop; - this.pollFrequencyInMillis = options.pollFrequencyInMillis; - this.trailing = options.trailing || false; - this.continuePollingOnError = options.continuePollingOnError || false; - this.pollFrequencyErrorMultiplier = options.pollFrequencyErrorMultiplier || 1; - this._timeoutId = null; - this._isRunning = false; - } - - getPollFrequency() { - return this.pollFrequencyInMillis; - } - - _poll() { - return this.functionToPoll() - .then(this.successFunction) - .then(() => { - if (!this._isRunning) { - return; - } - - this._timeoutId = setTimeout(this._poll.bind(this), this.pollFrequencyInMillis); - }) - .catch((e) => { - this.errorFunction(e); - if (!this._isRunning) { - return; - } - - if (this.continuePollingOnError) { - this._timeoutId = setTimeout( - this._poll.bind(this), - this.pollFrequencyInMillis * this.pollFrequencyErrorMultiplier - ); - } else { - this.stop(); - } - }); - } - - start() { - if (this._isRunning) { - return; - } - - this._isRunning = true; - if (this.trailing) { - this._timeoutId = setTimeout(this._poll.bind(this), this.pollFrequencyInMillis); - } else { - this._poll(); - } - } - - stop() { - if (!this._isRunning) { - return; - } - - this._isRunning = false; - clearTimeout(this._timeoutId); - this._timeoutId = null; - } - - isRunning() { - return this._isRunning; - } -} diff --git a/x-pack/legacy/plugins/xpack_main/index.js b/x-pack/legacy/plugins/xpack_main/index.js deleted file mode 100644 index a3bd66e744fda..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/index.js +++ /dev/null @@ -1,31 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { resolve } from 'path'; -import { setupXPackMain } from './server/lib/setup_xpack_main'; -import { xpackInfoRoute } from './server/routes/api/v1'; - -export const xpackMain = (kibana) => { - return new kibana.Plugin({ - id: 'xpack_main', - configPrefix: 'xpack.xpack_main', - publicDir: resolve(__dirname, 'public'), - require: [], - - config(Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - - init(server) { - setupXPackMain(server); - - // register routes - xpackInfoRoute(server); - }, - }); -}; diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js deleted file mode 100644 index f49f44bed97a7..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js +++ /dev/null @@ -1,68 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { BehaviorSubject } from 'rxjs'; -import sinon from 'sinon'; -import { XPackInfo } from '../xpack_info'; -import { setupXPackMain } from '../setup_xpack_main'; - -describe('setupXPackMain()', () => { - const sandbox = sinon.createSandbox(); - - let mockServer; - let mockStatusObservable; - let mockElasticsearchPlugin; - - beforeEach(() => { - sandbox.useFakeTimers(); - - mockElasticsearchPlugin = { - getCluster: sinon.stub(), - }; - - mockStatusObservable = sinon.stub({ subscribe() {} }); - - mockServer = sinon.stub({ - plugins: { - elasticsearch: mockElasticsearchPlugin, - }, - newPlatform: { - setup: { - core: { - status: { - core$: { - pipe() { - return mockStatusObservable; - }, - }, - }, - }, - plugins: { features: {}, licensing: { license$: new BehaviorSubject() } }, - }, - }, - events: { on() {} }, - log() {}, - config() {}, - expose() {}, - ext() {}, - }); - - // Make sure plugins doesn't consume config - const configGetStub = sinon - .stub() - .throws(new Error('`config.get` is called with unexpected key.')); - mockServer.config.returns({ get: configGetStub }); - }); - - afterEach(() => sandbox.restore()); - - it('all extension hooks should be properly initialized.', () => { - setupXPackMain(mockServer); - - sinon.assert.calledWithExactly(mockServer.expose, 'info', sinon.match.instanceOf(XPackInfo)); - sinon.assert.calledWithExactly(mockStatusObservable.subscribe, sinon.match.func); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js deleted file mode 100644 index 81fb822882817..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js +++ /dev/null @@ -1,398 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createHash } from 'crypto'; -import { BehaviorSubject } from 'rxjs'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { XPackInfo } from '../xpack_info'; -import { licensingMock } from '../../../../../../plugins/licensing/server/mocks'; - -function createLicense(license = {}, features = {}) { - return licensingMock.createLicense({ - license: { - uid: 'custom-uid', - type: 'gold', - mode: 'gold', - status: 'active', - expiryDateInMillis: 1286575200000, - ...license, - }, - features: { - security: { - description: 'Security for the Elastic Stack', - isAvailable: true, - isEnabled: true, - }, - watcher: { - description: 'Alerting, Notification and Automation for the Elastic Stack', - isAvailable: true, - isEnabled: false, - }, - ...features, - }, - }); -} - -function getSignature(object) { - return createHash('md5').update(JSON.stringify(object)).digest('hex'); -} - -describe('XPackInfo', () => { - let mockServer; - let mockElasticsearchPlugin; - - beforeEach(() => { - mockServer = sinon.stub({ - plugins: { elasticsearch: mockElasticsearchPlugin }, - events: { on() {} }, - newPlatform: { - setup: { - plugins: { - licensing: {}, - }, - }, - }, - }); - }); - - describe('refreshNow()', () => { - it('delegates to the new platform licensing plugin', async () => { - const refresh = sinon.spy(); - - const xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$: new BehaviorSubject(createLicense()), - refresh: refresh, - }, - }); - - await xPackInfo.refreshNow(); - - sinon.assert.calledOnce(refresh); - }); - }); - - describe('license', () => { - let xPackInfo; - let license$; - beforeEach(async () => { - license$ = new BehaviorSubject(createLicense()); - xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$, - refresh: () => null, - }, - }); - }); - - it('getUid() shows license uid returned from the license$.', async () => { - expect(xPackInfo.license.getUid()).to.be('custom-uid'); - - license$.next(createLicense({ uid: 'new-custom-uid' })); - - expect(xPackInfo.license.getUid()).to.be('new-custom-uid'); - - license$.next(createLicense({ uid: undefined, error: 'error-reason' })); - - expect(xPackInfo.license.getUid()).to.be(undefined); - }); - - it('isActive() is based on the status returned from the backend.', async () => { - expect(xPackInfo.license.isActive()).to.be(true); - - license$.next(createLicense({ status: 'expired' })); - expect(xPackInfo.license.isActive()).to.be(false); - - license$.next(createLicense({ status: 'some other value' })); - expect(xPackInfo.license.isActive()).to.be(false); - - license$.next(createLicense({ status: 'active' })); - expect(xPackInfo.license.isActive()).to.be(true); - - license$.next(createLicense({ status: undefined, error: 'error-reason' })); - expect(xPackInfo.license.isActive()).to.be(false); - }); - - it('getExpiryDateInMillis() is based on the value returned from the backend.', async () => { - expect(xPackInfo.license.getExpiryDateInMillis()).to.be(1286575200000); - - license$.next(createLicense({ expiryDateInMillis: 10203040 })); - expect(xPackInfo.license.getExpiryDateInMillis()).to.be(10203040); - - license$.next(createLicense({ expiryDateInMillis: undefined, error: 'error-reason' })); - expect(xPackInfo.license.getExpiryDateInMillis()).to.be(undefined); - }); - - it('getType() is based on the value returned from the backend.', async () => { - expect(xPackInfo.license.getType()).to.be('gold'); - - license$.next(createLicense({ type: 'basic' })); - expect(xPackInfo.license.getType()).to.be('basic'); - - license$.next(createLicense({ type: undefined, error: 'error-reason' })); - expect(xPackInfo.license.getType()).to.be(undefined); - }); - - it('isOneOf() correctly determines if current license is presented in the specified list.', async () => { - expect(xPackInfo.license.isOneOf('gold')).to.be(true); - expect(xPackInfo.license.isOneOf(['gold', 'basic'])).to.be(true); - expect(xPackInfo.license.isOneOf(['platinum', 'basic'])).to.be(false); - expect(xPackInfo.license.isOneOf('standard')).to.be(false); - - license$.next(createLicense({ mode: 'basic' })); - - expect(xPackInfo.license.isOneOf('basic')).to.be(true); - expect(xPackInfo.license.isOneOf(['gold', 'basic'])).to.be(true); - expect(xPackInfo.license.isOneOf(['platinum', 'gold'])).to.be(false); - expect(xPackInfo.license.isOneOf('standard')).to.be(false); - }); - }); - - describe('feature', () => { - let xPackInfo; - let license$; - beforeEach(async () => { - license$ = new BehaviorSubject( - createLicense( - {}, - { - feature: { - isAvailable: false, - isEnabled: true, - }, - } - ) - ); - xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$, - refresh: () => null, - }, - }); - }); - - it('isAvailable() checks whether particular feature is available.', async () => { - const availableFeatureOne = xPackInfo.feature('security'); - const availableFeatureTwo = xPackInfo.feature('watcher'); - const unavailableFeatureOne = xPackInfo.feature('feature'); - const unavailableFeatureTwo = xPackInfo.feature('non-existing-feature'); - - expect(availableFeatureOne.isAvailable()).to.be(true); - expect(availableFeatureTwo.isAvailable()).to.be(true); - expect(unavailableFeatureOne.isAvailable()).to.be(false); - expect(unavailableFeatureTwo.isAvailable()).to.be(false); - }); - - it('isEnabled() checks whether particular feature is enabled.', async () => { - const enabledFeatureOne = xPackInfo.feature('security'); - const enabledFeatureTwo = xPackInfo.feature('feature'); - const disabledFeatureOne = xPackInfo.feature('watcher'); - const disabledFeatureTwo = xPackInfo.feature('non-existing-feature'); - - expect(enabledFeatureOne.isEnabled()).to.be(true); - expect(enabledFeatureTwo.isEnabled()).to.be(true); - expect(disabledFeatureOne.isEnabled()).to.be(false); - expect(disabledFeatureTwo.isEnabled()).to.be(false); - }); - - it('registerLicenseCheckResultsGenerator() allows to fill in XPack Info feature specific info.', async () => { - const securityFeature = xPackInfo.feature('security'); - const watcherFeature = xPackInfo.feature('watcher'); - - expect(xPackInfo.toJSON().features.security).to.be(undefined); - expect(xPackInfo.toJSON().features.watcher).to.be(undefined); - - securityFeature.registerLicenseCheckResultsGenerator((info) => { - return { - isXPackInfo: info instanceof XPackInfo, - license: info.license.getType(), - someCustomValue: 100500, - }; - }); - - expect(xPackInfo.toJSON().features.security).to.eql({ - isXPackInfo: true, - license: 'gold', - someCustomValue: 100500, - }); - expect(xPackInfo.toJSON().features.watcher).to.be(undefined); - - watcherFeature.registerLicenseCheckResultsGenerator((info) => { - return { - isXPackInfo: info instanceof XPackInfo, - license: info.license.getType(), - someAnotherCustomValue: 500100, - }; - }); - - expect(xPackInfo.toJSON().features.security).to.eql({ - isXPackInfo: true, - license: 'gold', - someCustomValue: 100500, - }); - expect(xPackInfo.toJSON().features.watcher).to.eql({ - isXPackInfo: true, - license: 'gold', - someAnotherCustomValue: 500100, - }); - - license$.next(createLicense({ type: 'platinum' })); - - expect(xPackInfo.toJSON().features.security).to.eql({ - isXPackInfo: true, - license: 'platinum', - someCustomValue: 100500, - }); - expect(xPackInfo.toJSON().features.watcher).to.eql({ - isXPackInfo: true, - license: 'platinum', - someAnotherCustomValue: 500100, - }); - }); - - it('getLicenseCheckResults() correctly returns feature specific info.', async () => { - const securityFeature = xPackInfo.feature('security'); - const watcherFeature = xPackInfo.feature('watcher'); - - expect(securityFeature.getLicenseCheckResults()).to.be(undefined); - expect(watcherFeature.getLicenseCheckResults()).to.be(undefined); - - securityFeature.registerLicenseCheckResultsGenerator((info) => { - return { - isXPackInfo: info instanceof XPackInfo, - license: info.license.getType(), - someCustomValue: 100500, - }; - }); - - expect(securityFeature.getLicenseCheckResults()).to.eql({ - isXPackInfo: true, - license: 'gold', - someCustomValue: 100500, - }); - expect(watcherFeature.getLicenseCheckResults()).to.be(undefined); - - watcherFeature.registerLicenseCheckResultsGenerator((info) => { - return { - isXPackInfo: info instanceof XPackInfo, - license: info.license.getType(), - someAnotherCustomValue: 500100, - }; - }); - - expect(securityFeature.getLicenseCheckResults()).to.eql({ - isXPackInfo: true, - license: 'gold', - someCustomValue: 100500, - }); - expect(watcherFeature.getLicenseCheckResults()).to.eql({ - isXPackInfo: true, - license: 'gold', - someAnotherCustomValue: 500100, - }); - - license$.next(createLicense({ type: 'platinum' })); - - expect(securityFeature.getLicenseCheckResults()).to.eql({ - isXPackInfo: true, - license: 'platinum', - someCustomValue: 100500, - }); - expect(watcherFeature.getLicenseCheckResults()).to.eql({ - isXPackInfo: true, - license: 'platinum', - someAnotherCustomValue: 500100, - }); - }); - }); - - it('onLicenseInfoChange() allows to subscribe to license update', async () => { - const license$ = new BehaviorSubject(createLicense()); - - const xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$, - refresh: () => null, - }, - }); - - const watcherFeature = xPackInfo.feature('watcher'); - watcherFeature.registerLicenseCheckResultsGenerator((info) => ({ - type: info.license.getType(), - })); - - const statuses = []; - xPackInfo.onLicenseInfoChange(() => statuses.push(watcherFeature.getLicenseCheckResults())); - - license$.next(createLicense({ type: 'basic' })); - expect(statuses).to.eql([{ type: 'basic' }]); - - license$.next(createLicense({ type: 'trial' })); - expect(statuses).to.eql([{ type: 'basic' }, { type: 'trial' }]); - }); - - it('refreshNow() leads to onLicenseInfoChange()', async () => { - const license$ = new BehaviorSubject(createLicense()); - - const xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$, - refresh: () => license$.next({ type: 'basic' }), - }, - }); - - const watcherFeature = xPackInfo.feature('watcher'); - - watcherFeature.registerLicenseCheckResultsGenerator((info) => ({ - type: info.license.getType(), - })); - - const statuses = []; - xPackInfo.onLicenseInfoChange(() => statuses.push(watcherFeature.getLicenseCheckResults())); - - await xPackInfo.refreshNow(); - expect(statuses).to.eql([{ type: 'basic' }]); - }); - - it('getSignature() returns correct signature.', async () => { - const license$ = new BehaviorSubject(createLicense()); - const xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$, - refresh: () => null, - }, - }); - - expect(xPackInfo.getSignature()).to.be( - getSignature({ - license: { - type: 'gold', - isActive: true, - expiryDateInMillis: 1286575200000, - }, - features: {}, - }) - ); - - license$.next(createLicense({ type: 'platinum', expiryDateInMillis: 20304050 })); - - const expectedSignature = getSignature({ - license: { - type: 'platinum', - isActive: true, - expiryDateInMillis: 20304050, - }, - features: {}, - }); - expect(xPackInfo.getSignature()).to.be(expectedSignature); - - // Should stay the same after refresh if nothing changed. - license$.next(createLicense({ type: 'platinum', expiryDateInMillis: 20304050 })); - - expect(xPackInfo.getSignature()).to.be(expectedSignature); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js b/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js deleted file mode 100644 index fd4e3c86d0ca7..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pairwise } from 'rxjs/operators'; -import { XPackInfo } from './xpack_info'; - -/** - * Setup the X-Pack Main plugin. This is fired every time that the Elasticsearch plugin becomes Green. - * - * This will ensure that X-Pack is installed on the Elasticsearch cluster, as well as trigger the initial - * polling for _xpack/info. - * - * @param server {Object} The Kibana server object. - */ -export function setupXPackMain(server) { - const info = new XPackInfo(server, { licensing: server.newPlatform.setup.plugins.licensing }); - - server.expose('info', info); - - // trigger an xpack info refresh whenever the elasticsearch plugin status changes - server.newPlatform.setup.core.status.core$ - .pipe(pairwise()) - .subscribe(async ([coreLast, coreCurrent]) => { - if (coreLast.elasticsearch.level !== coreCurrent.elasticsearch.level) { - await info.refreshNow(); - } - }); - - return info; -} diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts deleted file mode 100644 index aa66532a2897d..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts +++ /dev/null @@ -1,240 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createHash } from 'crypto'; -import { Legacy } from 'kibana'; - -import { XPackInfoLicense } from './xpack_info_license'; - -import { LicensingPluginSetup, ILicense } from '../../../../../plugins/licensing/server'; - -export interface XPackInfoOptions { - clusterSource?: string; - pollFrequencyInMillis: number; -} - -type LicenseGeneratorCheck = (xpackInfo: XPackInfo) => any; - -export interface XPackFeature { - isAvailable(): boolean; - isEnabled(): boolean; - registerLicenseCheckResultsGenerator(generator: LicenseGeneratorCheck): void; - getLicenseCheckResults(): any; -} - -interface Deps { - licensing: LicensingPluginSetup; -} - -/** - * A helper that provides a convenient way to access XPack Info returned by Elasticsearch. - */ -export class XPackInfo { - /** - * XPack License object. - * @type {XPackInfoLicense} - * @private - */ - _license: XPackInfoLicense; - - /** - * Feature name <-> feature license check generator function mapping. - * @type {Map} - * @private - */ - _featureLicenseCheckResultsGenerators = new Map(); - - /** - * Set of listener functions that will be called whenever the license - * info changes - * @type {Set} - */ - _licenseInfoChangedListeners = new Set<() => void>(); - - /** - * Cache that may contain last xpack info API response or error, json representation - * of xpack info and xpack info signature. - * @type {{response: Object|undefined, error: Object|undefined, json: Object|undefined, signature: string|undefined}} - * @private - */ - private _cache: { - license?: ILicense; - error?: string; - json?: Record; - signature?: string; - }; - - /** - * XPack License instance. - * @returns {XPackInfoLicense} - */ - public get license() { - return this._license; - } - - private readonly licensingPlugin: LicensingPluginSetup; - - /** - * Constructs XPack info object. - * @param {Hapi.Server} server HapiJS server instance. - */ - constructor(server: Legacy.Server, deps: Deps) { - if (!deps.licensing) { - throw new Error('XPackInfo requires enabled Licensing plugin'); - } - this.licensingPlugin = deps.licensing; - - this._cache = {}; - - this.licensingPlugin.license$.subscribe((license: ILicense) => { - if (license.isActive) { - this._cache = { - license, - error: undefined, - }; - } else { - this._cache = { - license, - error: license.error, - }; - } - - this._licenseInfoChangedListeners.forEach((fn) => fn()); - }); - - this._license = new XPackInfoLicense(() => this._cache.license); - } - - /** - * Checks whether XPack info is available. - * @returns {boolean} - */ - isAvailable() { - return Boolean(this._cache.license?.isAvailable); - } - - /** - * Checks whether ES was available - * @returns {boolean} - */ - isXpackUnavailable() { - return ( - this._cache.error && - this._cache.error === 'X-Pack plugin is not installed on the Elasticsearch cluster.' - ); - } - - /** - * If present, describes the reason why XPack info is not available. - * @returns {Error|string} - */ - unavailableReason() { - return this._cache.license?.getUnavailableReason(); - } - - onLicenseInfoChange(handler: () => void) { - this._licenseInfoChangedListeners.add(handler); - } - - /** - * Queries server to get the updated XPack info. - * @returns {Promise.} - */ - async refreshNow() { - await this.licensingPlugin.refresh(); - return this; - } - - /** - * Returns a wrapper around XPack info that gives an access to the properties of - * the specific feature. - * @param {string} name Name of the feature to get a wrapper for. - * @returns {Object} - */ - feature(name: string): XPackFeature { - return { - /** - * Checks whether feature is available (permitted by the current license). - * @returns {boolean} - */ - isAvailable: () => { - return Boolean(this._cache.license?.getFeature(name).isAvailable); - }, - - /** - * Checks whether feature is enabled (not disabled by the configuration specifically). - * @returns {boolean} - */ - isEnabled: () => { - return Boolean(this._cache.license?.getFeature(name).isEnabled); - }, - - /** - * Registers a `generator` function that will be called with XPackInfo instance as - * argument whenever XPack info changes. Whatever `generator` returns will be stored - * in XPackInfo JSON representation and can be accessed with `getLicenseCheckResults`. - * @param {Function} generator Function to call whenever XPackInfo changes. - */ - registerLicenseCheckResultsGenerator: (generator: LicenseGeneratorCheck) => { - this._featureLicenseCheckResultsGenerators.set(name, generator); - - // Since JSON representation and signature are cached we should invalidate them to - // include results from newly registered generator when they are requested. - this._cache.json = undefined; - this._cache.signature = undefined; - }, - - /** - * Returns license check results that were previously produced by the `generator` function. - * @returns {Object} - */ - getLicenseCheckResults: () => this.toJSON().features[name], - }; - } - - /** - * Extracts string md5 hash from the stringified version of license JSON representation. - * @returns {string} - */ - getSignature() { - if (this._cache.signature) { - return this._cache.signature; - } - - this._cache.signature = createHash('md5').update(JSON.stringify(this.toJSON())).digest('hex'); - - return this._cache.signature; - } - - /** - * Returns JSON representation of the license object that is suitable for serialization. - * @returns {Object} - */ - toJSON() { - if (this._cache.json) { - return this._cache.json; - } - - this._cache.json = { - license: { - type: this.license.getType(), - isActive: this.license.isActive(), - expiryDateInMillis: this.license.getExpiryDateInMillis(), - }, - features: {}, - }; - - // Set response elements specific to each feature. To do this, - // call the license check results generator for each feature, passing them - // the xpack info object - for (const [feature, licenseChecker] of this._featureLicenseCheckResultsGenerators) { - // return value expected to be a dictionary object. - this._cache.json.features[feature] = licenseChecker(this); - } - - return this._cache.json; - } -} diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js deleted file mode 100644 index ccb5742216ca7..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js +++ /dev/null @@ -1,207 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { licensingMock } from '../../../../../plugins/licensing/server/mocks'; -import { XPackInfoLicense } from './xpack_info_license'; - -function getXPackInfoLicense(getRawLicense) { - return new XPackInfoLicense(getRawLicense); -} - -describe('XPackInfoLicense', () => { - const xpackInfoLicenseUndefined = getXPackInfoLicense(() => {}); - let xpackInfoLicense; - let getRawLicense; - - beforeEach(() => { - getRawLicense = jest.fn(); - xpackInfoLicense = getXPackInfoLicense(getRawLicense); - }); - - test('getUid returns uid field', () => { - const uid = 'abc123'; - - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { uid } })); - - expect(xpackInfoLicense.getUid()).toBe(uid); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - expect(xpackInfoLicenseUndefined.getUid()).toBe(undefined); - }); - - test('isActive returns true if status is active', () => { - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { status: 'active' } })); - - expect(xpackInfoLicense.isActive()).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(1); - }); - - test('isActive returns false if status is not active', () => { - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { status: 'aCtIvE' } })); // needs to match exactly - - expect(xpackInfoLicense.isActive()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - expect(xpackInfoLicenseUndefined.isActive()).toBe(false); - }); - - test('getExpiryDateInMillis returns expiry_date_in_millis', () => { - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { expiryDateInMillis: 123 } }) - ); - - expect(xpackInfoLicense.getExpiryDateInMillis()).toBe(123); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - expect(xpackInfoLicenseUndefined.getExpiryDateInMillis()).toBe(undefined); - }); - - test('isOneOf returns true of the mode includes one of the types', () => { - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { mode: 'platinum' } })); - - expect(xpackInfoLicense.isOneOf('platinum')).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - expect(xpackInfoLicense.isOneOf(['platinum'])).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(2); - expect(xpackInfoLicense.isOneOf(['gold', 'platinum'])).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(3); - expect(xpackInfoLicense.isOneOf(['platinum', 'gold'])).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(4); - expect(xpackInfoLicense.isOneOf(['basic', 'gold'])).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(5); - expect(xpackInfoLicense.isOneOf(['basic'])).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(6); - - expect(xpackInfoLicenseUndefined.isOneOf(['platinum', 'gold'])).toBe(false); - }); - - test('getType returns the type', () => { - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { type: 'basic' } })); - - expect(xpackInfoLicense.getType()).toBe('basic'); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { type: 'gold' } })); - - expect(xpackInfoLicense.getType()).toBe('gold'); - expect(getRawLicense).toHaveBeenCalledTimes(2); - - expect(xpackInfoLicenseUndefined.getType()).toBe(undefined); - }); - - test('getMode returns the mode', () => { - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { mode: 'basic' } })); - - expect(xpackInfoLicense.getMode()).toBe('basic'); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { mode: 'gold' } })); - - expect(xpackInfoLicense.getMode()).toBe('gold'); - expect(getRawLicense).toHaveBeenCalledTimes(2); - - expect(xpackInfoLicenseUndefined.getMode()).toBe(undefined); - }); - - test('isActiveLicense returns the true if active and typeChecker matches', () => { - const expectAbc123 = (type) => type === 'abc123'; - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'abc123' } }) - ); - - expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'abc123' } }) - ); - - expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(2); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'NOTabc123' } }) - ); - - expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(3); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'NOTabc123' } }) - ); - - expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(4); - - expect(xpackInfoLicenseUndefined.isActive(expectAbc123)).toBe(false); - }); - - test('isBasic returns the true if active and basic', () => { - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'basic' } }) - ); - - expect(xpackInfoLicense.isBasic()).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'gold' } }) - ); - - expect(xpackInfoLicense.isBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(2); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'trial' } }) - ); - - expect(xpackInfoLicense.isBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(3); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'platinum' } }) - ); - - expect(xpackInfoLicense.isBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(4); - - expect(xpackInfoLicenseUndefined.isBasic()).toBe(false); - }); - - test('isNotBasic returns the true if active and not basic', () => { - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'platinum' } }) - ); - - expect(xpackInfoLicense.isNotBasic()).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'gold' } }) - ); - - expect(xpackInfoLicense.isNotBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(2); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'trial' } }) - ); - - expect(xpackInfoLicense.isNotBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(3); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'basic' } }) - ); - - expect(xpackInfoLicense.isNotBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(4); - - expect(xpackInfoLicenseUndefined.isNotBasic()).toBe(false); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.ts b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.ts deleted file mode 100644 index dd53f63909475..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.ts +++ /dev/null @@ -1,111 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ILicense } from '../../../../../plugins/licensing/server'; - -/** - * "View" for XPack Info license information. - */ -export class XPackInfoLicense { - /** - * Function that retrieves license information from the XPack info object. - * @type {Function} - * @private - */ - _getRawLicense: () => ILicense | undefined; - - constructor(getRawLicense: () => ILicense | undefined) { - this._getRawLicense = getRawLicense; - } - - /** - * Returns unique identifier of the license. - * @returns {string|undefined} - */ - getUid() { - return this._getRawLicense()?.uid; - } - - /** - * Indicates whether license is still active. - * @returns {boolean} - */ - isActive() { - return Boolean(this._getRawLicense()?.isActive); - } - - /** - * Returns license expiration date in ms. - * - * Note: A basic license created after 6.3 will have no expiration, thus returning undefined. - * - * @returns {number|undefined} - */ - getExpiryDateInMillis() { - return this._getRawLicense()?.expiryDateInMillis; - } - - /** - * Checks if the license is represented in a specified license list. - * @param {String} candidateLicenses List of the licenses to check against. - * @returns {boolean} - */ - isOneOf(candidateLicenses: string | string[]) { - const candidates = Array.isArray(candidateLicenses) ? candidateLicenses : [candidateLicenses]; - const mode = this._getRawLicense()?.mode; - return Boolean(mode && candidates.includes(mode)); - } - - /** - * Returns type of the license (basic, gold etc.). - * @returns {string|undefined} - */ - getType() { - return this._getRawLicense()?.type; - } - - /** - * Returns mode of the license (basic, gold etc.). This is the "effective" type of the license. - * @returns {string|undefined} - */ - getMode() { - return this._getRawLicense()?.mode; - } - - /** - * Determine if the current license is active and the supplied {@code type}. - * - * @param {Function} typeChecker The license type checker. - * @returns {boolean} - */ - isActiveLicense(typeChecker: (mode: string) => boolean) { - const license = this._getRawLicense(); - - return Boolean(license?.isActive && typeChecker(license.mode as any)); - } - - /** - * Determine if the license is an active, basic license. - * - * Note: This also verifies that the license is active. Therefore it is not safe to assume that !isBasic() === isNotBasic(). - * - * @returns {boolean} - */ - isBasic() { - return this.isActiveLicense((mode) => mode === 'basic'); - } - - /** - * Determine if the license is an active, non-basic license (e.g., standard, gold, platinum, or trial). - * - * Note: This also verifies that the license is active. Therefore it is not safe to assume that !isBasic() === isNotBasic(). - * - * @returns {boolean} - */ - isNotBasic() { - return this.isActiveLicense((mode) => mode !== 'basic'); - } -} diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/__tests__/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/__tests__/xpack_info.js deleted file mode 100644 index 540d9f63ea6c8..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/__tests__/xpack_info.js +++ /dev/null @@ -1,85 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { xpackInfoRoute } from '../xpack_info'; - -describe('XPackInfo routes', () => { - let serverStub; - beforeEach(() => { - serverStub = { - route: sinon.stub(), - plugins: { - xpack_main: { - info: sinon.stub({ isAvailable() {}, toJSON() {} }), - }, - }, - }; - - xpackInfoRoute(serverStub); - }); - - it('correctly initialize XPack Info route.', () => { - sinon.assert.calledWithExactly(serverStub.route, { - method: 'GET', - path: '/api/xpack/v1/info', - handler: sinon.match.func, - }); - }); - - it('replies with `Not Found` Boom error if `xpackInfo` is not available.', () => { - serverStub.plugins.xpack_main.info.isAvailable.returns(false); - - const onRouteHandler = serverStub.route.firstCall.args[0].handler; - const response = onRouteHandler(); - - expect(response.isBoom).to.be(true); - expect(response.message).to.be('Not Found'); - expect(response.output.statusCode).to.be(404); - }); - - it('replies with pre-processed `xpackInfo` if it is available.', () => { - serverStub.plugins.xpack_main.info.isAvailable.returns(true); - serverStub.plugins.xpack_main.info.toJSON.returns({ - license: { - type: 'gold', - isActive: true, - expiryDateInMillis: 1509368280381, - }, - features: { - security: { - showLogin: true, - allowLogin: true, - showLinks: false, - allowRoleDocumentLevelSecurity: false, - allowRoleFieldLevelSecurity: false, - }, - }, - }); - - const onRouteHandler = serverStub.route.firstCall.args[0].handler; - const response = onRouteHandler(); - - expect(response).to.eql({ - license: { - type: 'gold', - is_active: true, - expiry_date_in_millis: 1509368280381, - }, - features: { - security: { - show_login: true, - allow_login: true, - show_links: false, - allow_role_document_level_security: false, - allow_role_field_level_security: false, - }, - }, - }); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/xpack_info.js deleted file mode 100644 index 3cc57ae9fcab4..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/xpack_info.js +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; -import { convertKeysToSnakeCaseDeep } from '../../../../../../server/lib/key_case_converter'; - -/* - * A route to provide the basic XPack info for the production cluster - */ -export function xpackInfoRoute(server) { - server.route({ - method: 'GET', - path: '/api/xpack/v1/info', - handler() { - const xPackInfo = server.plugins.xpack_main.info; - - return xPackInfo.isAvailable() - ? convertKeysToSnakeCaseDeep(xPackInfo.toJSON()) - : Boom.notFound(); - }, - }); -} diff --git a/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts b/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts deleted file mode 100644 index c2ec5662ad12e..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import KbnServer from 'src/legacy/server/kbn_server'; -import { KibanaFeature } from '../../../../plugins/features/server'; -import { XPackInfo, XPackInfoOptions } from './lib/xpack_info'; -export { XPackFeature } from './lib/xpack_info'; - -export interface XPackMainPlugin { - info: XPackInfo; -} diff --git a/x-pack/legacy/server/lib/__tests__/key_case_converter.js b/x-pack/legacy/server/lib/__tests__/key_case_converter.js deleted file mode 100644 index 7ed9fa668ae66..0000000000000 --- a/x-pack/legacy/server/lib/__tests__/key_case_converter.js +++ /dev/null @@ -1,117 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { convertKeysToSnakeCaseDeep, convertKeysToCamelCaseDeep } from '../key_case_converter'; - -describe('key_case_converter', () => { - let testObject; - - beforeEach(() => { - testObject = { - topLevelKey1: { - innerLevelKey1: 17, - inner_level_key2: [19, 31], - }, - top_level_key2: { - innerLevelKey1: 'foo_fooFoo', - inner_level_key2: [{ foo_bar: 29 }, { barBar: 37 }], - }, - }; - }); - - describe('convertKeysToSnakeCaseDeep', () => { - it('should recursively convert camelCase keys to snake_case keys', () => { - const expectedResultObject = { - top_level_key_1: { - inner_level_key_1: 17, - inner_level_key_2: [19, 31], - }, - top_level_key_2: { - inner_level_key_1: 'foo_fooFoo', - inner_level_key_2: [{ foo_bar: 29 }, { bar_bar: 37 }], - }, - }; - expect(convertKeysToSnakeCaseDeep(testObject)).to.eql(expectedResultObject); - }); - - it('should not modify original object', () => { - convertKeysToSnakeCaseDeep(testObject); - expect(Object.keys(testObject)).to.contain('topLevelKey1'); - expect(Object.keys(testObject.topLevelKey1)).to.contain('innerLevelKey1'); - }); - - it('should preserve inner arrays', () => { - const result = convertKeysToSnakeCaseDeep(testObject); - expect(testObject.topLevelKey1.inner_level_key2).to.be.an(Array); - expect(result.top_level_key_1.inner_level_key_2).to.be.an(Array); - }); - - it('should preserve top-level arrays', () => { - testObject = [{ foo_bar: 17 }, [19, { barBaz: 'qux' }]]; - const expectedResultObject = [{ foo_bar: 17 }, [19, { bar_baz: 'qux' }]]; - const result = convertKeysToSnakeCaseDeep(testObject); - expect(testObject).to.be.an(Array); - expect(testObject[1]).to.be.an(Array); - expect(result).to.be.an(Array); - expect(result[1]).to.be.an(Array); - expect(result).to.eql(expectedResultObject); - }); - - it('should throw an error if something other an object or array is passed in', () => { - const expectedErrorMessageRegexp = /Specified object should be an Object or Array/; - expect(convertKeysToSnakeCaseDeep) - .withArgs('neither an object nor an array') - .to.throwException(expectedErrorMessageRegexp); - }); - }); - - describe('convertKeysToCamelCaseDeep', () => { - it('should recursively convert snake_case keys to camelCase keys', () => { - const expectedResultObject = { - topLevelKey1: { - innerLevelKey1: 17, - innerLevelKey2: [19, 31], - }, - topLevelKey2: { - innerLevelKey1: 'foo_fooFoo', - innerLevelKey2: [{ fooBar: 29 }, { barBar: 37 }], - }, - }; - expect(convertKeysToCamelCaseDeep(testObject)).to.eql(expectedResultObject); - }); - - it('should not modify original object', () => { - convertKeysToCamelCaseDeep(testObject); - expect(Object.keys(testObject)).to.contain('top_level_key2'); - expect(Object.keys(testObject.topLevelKey1)).to.contain('inner_level_key2'); - }); - - it('should preserve inner arrays', () => { - const result = convertKeysToCamelCaseDeep(testObject); - expect(testObject.topLevelKey1.inner_level_key2).to.be.an(Array); - expect(result.topLevelKey1.innerLevelKey2).to.be.an(Array); - }); - - it('should preserve top-level arrays', () => { - testObject = [{ foo_bar: 17 }, [19, { barBaz: 'qux' }]]; - const expectedResultObject = [{ fooBar: 17 }, [19, { barBaz: 'qux' }]]; - const result = convertKeysToCamelCaseDeep(testObject); - expect(testObject).to.be.an(Array); - expect(testObject[1]).to.be.an(Array); - expect(result).to.be.an(Array); - expect(result[1]).to.be.an(Array); - expect(result).to.eql(expectedResultObject); - }); - - it('should throw an error if something other an object or array is passed in', () => { - const expectedErrorMessageRegexp = /Specified object should be an Object or Array/; - expect(convertKeysToCamelCaseDeep) - .withArgs('neither an object nor an array') - .to.throwException(expectedErrorMessageRegexp); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/__tests__/kibana_state.js b/x-pack/legacy/server/lib/__tests__/kibana_state.js deleted file mode 100644 index d1b4142b10446..0000000000000 --- a/x-pack/legacy/server/lib/__tests__/kibana_state.js +++ /dev/null @@ -1,129 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import rison from 'rison-node'; -import { parseKibanaState } from '../parse_kibana_state'; - -const stateIndices = { - global: '_g', - app: '_a', -}; -const globalTime = - '(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))'; - -describe('Kibana state', function () { - describe('type checking', function () { - it('should throw if not given an object', function () { - const fn = () => parseKibanaState('i am not an object', 'global'); - const fn2 = () => parseKibanaState(['arrays are not valid either'], 'global'); - expect(fn).to.throwException(/must be an object/i); - expect(fn2).to.throwException(/must be an object/i); - }); - - it('should throw with invalid type', function () { - const fn = () => parseKibanaState({}, 'this is an invalid state type'); - expect(fn).to.throwException(/unknown state type/i); - }); - }); - - describe('value of exists', function () { - it('should be false if state does not exist', function () { - const state = parseKibanaState({}, 'global'); - expect(state.exists).to.equal(false); - }); - - it('should be true if state exists', function () { - const query = {}; - query[stateIndices.global] = rison.encode({ hello: 'world' }); - const state = parseKibanaState(query, 'global'); - expect(state.exists).to.equal(true); - }); - }); - - describe('instance methods', function () { - let query; - - beforeEach(function () { - query = {}; - query[stateIndices.global] = globalTime; - }); - - describe('get', function () { - it('should return the value', function () { - const state = parseKibanaState(query, 'global'); - const { refreshInterval } = rison.decode(globalTime); - expect(state.get('refreshInterval')).to.eql(refreshInterval); - }); - - it('should use the default value for missing props', function () { - const defaultValue = 'default value'; - const state = parseKibanaState(query, 'global'); - expect(state.get('no such value', defaultValue)).to.equal(defaultValue); - }); - }); - - describe('set', function () { - it('should update the value of the state', function () { - const state = parseKibanaState(query, 'global'); - expect(state.get('refreshInterval.pause')).to.equal(false); - - state.set(['refreshInterval', 'pause'], true); - expect(state.get('refreshInterval.pause')).to.equal(true); - }); - - it('should create new properties', function () { - const prop = 'newProp'; - const value = 12345; - const state = parseKibanaState(query, 'global'); - expect(state.get(prop)).to.be(undefined); - - state.set(prop, value); - expect(state.get(prop)).to.not.be(undefined); - expect(state.get(prop)).to.equal(value); - }); - }); - - describe('removing properties', function () { - it('should remove a single value', function () { - const state = parseKibanaState(query, 'global'); - expect(state.get('refreshInterval')).to.be.an('object'); - - state.removeProps('refreshInterval'); - expect(state.get('refreshInterval')).to.be(undefined); - }); - - it('should remove multiple values', function () { - const state = parseKibanaState(query, 'global'); - expect(state.get('refreshInterval')).to.be.an('object'); - expect(state.get('time')).to.be.an('object'); - - state.removeProps(['refreshInterval', 'time']); - expect(state.get('refreshInterval')).to.be(undefined); - expect(state.get('time')).to.be(undefined); - }); - }); - - describe('toString', function () { - it('should rison encode the state', function () { - const state = parseKibanaState(query, 'global'); - expect(state.toString()).to.equal(globalTime); - }); - }); - - describe('toQuery', function () { - it('should return an object', function () { - const state = parseKibanaState(query, 'global'); - expect(state.toQuery()).to.be.an('object'); - }); - - it('should contain the kibana state property', function () { - const state = parseKibanaState(query, 'global'); - expect(state.toQuery()).to.have.property(stateIndices.global, globalTime); - }); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/check_license/check_license.js b/x-pack/legacy/server/lib/check_license/check_license.js deleted file mode 100644 index 7695755622310..0000000000000 --- a/x-pack/legacy/server/lib/check_license/check_license.js +++ /dev/null @@ -1,75 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { - LICENSE_STATUS_UNAVAILABLE, - LICENSE_STATUS_INVALID, - LICENSE_STATUS_EXPIRED, - LICENSE_STATUS_VALID, - RANKED_LICENSE_TYPES, -} from '../../../common/constants'; - -export function checkLicense(pluginName, minimumLicenseRequired, xpackLicenseInfo) { - if (!minimumLicenseRequired) { - throw new Error( - `Error checking license for plugin "${pluginName}". The minimum license required has not been provided.` - ); - } - - if (!RANKED_LICENSE_TYPES.includes(minimumLicenseRequired)) { - throw new Error(`Invalid license type supplied to checkLicense: ${minimumLicenseRequired}`); - } - - // If, for some reason, we cannot get the license information - // from Elasticsearch, assume worst case and disable - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - status: LICENSE_STATUS_UNAVAILABLE, - message: i18n.translate('xpack.server.checkLicense.errorUnavailableMessage', { - defaultMessage: - 'You cannot use {pluginName} because license information is not available at this time.', - values: { pluginName }, - }), - }; - } - - const { license } = xpackLicenseInfo; - const isLicenseModeValid = license.isOneOf( - [...RANKED_LICENSE_TYPES].splice(RANKED_LICENSE_TYPES.indexOf(minimumLicenseRequired)) - ); - const isLicenseActive = license.isActive(); - const licenseType = license.getType(); - - // License is not valid - if (!isLicenseModeValid) { - return { - status: LICENSE_STATUS_INVALID, - message: i18n.translate('xpack.server.checkLicense.errorUnsupportedMessage', { - defaultMessage: - 'Your {licenseType} license does not support {pluginName}. Please upgrade your license.', - values: { licenseType, pluginName }, - }), - }; - } - - // License is valid but not active - if (!isLicenseActive) { - return { - status: LICENSE_STATUS_EXPIRED, - message: i18n.translate('xpack.server.checkLicense.errorExpiredMessage', { - defaultMessage: - 'You cannot use {pluginName} because your {licenseType} license has expired.', - values: { licenseType, pluginName }, - }), - }; - } - - // License is valid and active - return { - status: LICENSE_STATUS_VALID, - }; -} diff --git a/x-pack/legacy/server/lib/check_license/check_license.test.js b/x-pack/legacy/server/lib/check_license/check_license.test.js deleted file mode 100644 index 65b599ed4a5f6..0000000000000 --- a/x-pack/legacy/server/lib/check_license/check_license.test.js +++ /dev/null @@ -1,132 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { set } from '@elastic/safer-lodash-set'; -import { checkLicense } from './check_license'; -import { - LICENSE_STATUS_UNAVAILABLE, - LICENSE_STATUS_EXPIRED, - LICENSE_STATUS_VALID, - LICENSE_TYPE_BASIC, -} from '../../../common/constants'; - -describe('check_license', function () { - const pluginName = 'Foo'; - const minimumLicenseRequired = LICENSE_TYPE_BASIC; - let mockLicenseInfo; - beforeEach(() => (mockLicenseInfo = {})); - - describe('license information is undefined', () => { - beforeEach(() => (mockLicenseInfo = undefined)); - - it('should set status to unavailable', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_UNAVAILABLE - ); - }); - - it('should set a message', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).not.toBe( - undefined - ); - }); - }); - - describe('license information is not available', () => { - beforeEach(() => (mockLicenseInfo.isAvailable = () => false)); - - it('should set status to unavailable', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_UNAVAILABLE - ); - }); - - it('should set a message', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).not.toBe( - undefined - ); - }); - }); - - describe('license information is available', () => { - beforeEach(() => { - mockLicenseInfo.isAvailable = () => true; - set(mockLicenseInfo, 'license.getType', () => LICENSE_TYPE_BASIC); - }); - - describe('& license is trial, standard, gold, platinum', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set status to valid', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_VALID - ); - }); - - it('should not set a message', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).toBe( - undefined - ); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set status to inactive', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_EXPIRED - ); - }); - - it('should set a message', () => { - expect( - checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message - ).not.toBe(undefined); - }); - }); - }); - - describe('& license is basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set status to valid', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_VALID - ); - }); - - it('should not set a message', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).toBe( - undefined - ); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set status to inactive', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_EXPIRED - ); - }); - - it('should set a message', () => { - expect( - checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message - ).not.toBe(undefined); - }); - }); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/call_with_request_factory/call_with_request_factory.js b/x-pack/legacy/server/lib/create_router/call_with_request_factory/call_with_request_factory.js deleted file mode 100644 index df1ce95b31655..0000000000000 --- a/x-pack/legacy/server/lib/create_router/call_with_request_factory/call_with_request_factory.js +++ /dev/null @@ -1,12 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const callWithRequestFactory = (server, pluginId, config) => { - const { callWithRequest } = config - ? server.plugins.elasticsearch.createCluster(pluginId, config) - : server.plugins.elasticsearch.getCluster('data'); - return callWithRequest; -}; diff --git a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.d.ts b/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.d.ts deleted file mode 100644 index 3537d1bf42079..0000000000000 --- a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.d.ts +++ /dev/null @@ -1,18 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Legacy } from 'kibana'; -import { LegacyAPICaller } from '../../../../../../src/core/server'; - -export type CallWithRequest = (...args: any[]) => LegacyAPICaller; - -export declare function callWithRequestFactory( - server: Legacy.Server, - pluginId: string, - config?: { - plugins: any[]; - } -): CallWithRequest; diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_custom_error.js deleted file mode 100644 index f9c102be7a1ff..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_custom_error.js +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapCustomError } from '../wrap_custom_error'; - -describe('wrap_custom_error', () => { - describe('#wrapCustomError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const statusCode = 404; - const wrappedError = wrapCustomError(originalError, statusCode); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.output.statusCode).to.equal(statusCode); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_es_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100644 index 8241dc4329137..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_es_error.js +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapEsError } from '../wrap_es_error'; - -describe('wrap_es_error', () => { - describe('#wrapEsError', () => { - let originalError; - beforeEach(() => { - originalError = new Error('I am an error'); - originalError.statusCode = 404; - originalError.response = '{}'; - }); - - it('should return a Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - - it('should return the correct Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be(originalError.message); - }); - - it('should return the correct Boom object with custom message', () => { - const wrappedError = wrapEsError(originalError, { 404: 'No encontrado!' }); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be('No encontrado!'); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_unknown_error.js deleted file mode 100644 index 85e0b2b3033ad..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_unknown_error.js +++ /dev/null @@ -1,19 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapUnknownError } from '../wrap_unknown_error'; - -describe('wrap_unknown_error', () => { - describe('#wrapUnknownError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const wrappedError = wrapUnknownError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/index.d.ts b/x-pack/legacy/server/lib/create_router/error_wrappers/index.d.ts deleted file mode 100644 index 1aaefb4e3727c..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/index.d.ts +++ /dev/null @@ -1,12 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import Boom from 'boom'; - -export declare function wrapCustomError(error: Error, statusCode: number): Boom; - -export declare function wrapEsError(error: Error, statusCodeToMessageMap?: object): Boom; - -export declare function wrapUnknownError(error: Error): Boom; diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/index.js b/x-pack/legacy/server/lib/create_router/error_wrappers/index.js deleted file mode 100644 index f275f15637091..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/index.js +++ /dev/null @@ -1,9 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { wrapCustomError } from './wrap_custom_error'; -export { wrapEsError } from './wrap_es_error'; -export { wrapUnknownError } from './wrap_unknown_error'; diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_custom_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_custom_error.js deleted file mode 100644 index 3295113d38ee5..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_custom_error.js +++ /dev/null @@ -1,18 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps a custom error into a Boom error response and returns it - * - * @param err Object error - * @param statusCode Error status code - * @return Object Boom error response - */ -export function wrapCustomError(err, statusCode) { - return Boom.boomify(err, { statusCode }); -} diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_es_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_es_error.js deleted file mode 100644 index 72be6321af8a2..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_es_error.js +++ /dev/null @@ -1,59 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -function extractCausedByChain(causedBy = {}, accumulator = []) { - const { reason, caused_by } = causedBy; // eslint-disable-line camelcase - - if (reason) { - accumulator.push(reason); - } - - // eslint-disable-next-line camelcase - if (caused_by) { - return extractCausedByChain(caused_by, accumulator); - } - - return accumulator; -} - -/** - * Wraps an error thrown by the ES JS client into a Boom error response and returns it - * - * @param err Object Error thrown by ES JS client - * @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages - * @return Object Boom error response - */ -export function wrapEsError(err, statusCodeToMessageMap = {}) { - const { statusCode, response } = err; - - const { - error: { - root_cause = [], // eslint-disable-line camelcase - caused_by, // eslint-disable-line camelcase - } = {}, - } = JSON.parse(response); - - // If no custom message if specified for the error's status code, just - // wrap the error as a Boom error response, include the additional information from ES, and return it - if (!statusCodeToMessageMap[statusCode]) { - const boomError = Boom.boomify(err, { statusCode }); - - // The caused_by chain has the most information so use that if it's available. If not then - // settle for the root_cause. - const causedByChain = extractCausedByChain(caused_by); - const defaultCause = root_cause.length ? extractCausedByChain(root_cause[0]) : undefined; - - boomError.output.payload.cause = causedByChain.length ? causedByChain : defaultCause; - return boomError; - } - - // Otherwise, use the custom message to create a Boom error response and - // return it - const message = statusCodeToMessageMap[statusCode]; - return new Boom(message, { statusCode }); -} diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_unknown_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_unknown_error.js deleted file mode 100644 index ffd915c513362..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_unknown_error.js +++ /dev/null @@ -1,17 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps an unknown error into a Boom error response and returns it - * - * @param err Object Unknown error - * @return Object Boom error response - */ -export function wrapUnknownError(err) { - return Boom.boomify(err); -} diff --git a/x-pack/legacy/server/lib/create_router/index.d.ts b/x-pack/legacy/server/lib/create_router/index.d.ts deleted file mode 100644 index 76e5f4b599708..0000000000000 --- a/x-pack/legacy/server/lib/create_router/index.d.ts +++ /dev/null @@ -1,38 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { Request, ResponseToolkit } from 'hapi'; -import { Legacy } from 'kibana'; -import { CallWithRequest } from './call_with_request_factory'; - -export * from './error_wrappers'; - -export type RouterRouteHandler = ( - req: Request, - callWithRequest: ReturnType, - responseToolkit: ResponseToolkit -) => Promise; - -export type RouterRoute = (path: string, handler: RouterRouteHandler) => Router; - -export interface Router { - get: RouterRoute; - post: RouterRoute; - put: RouterRoute; - delete: RouterRoute; - patch: RouterRoute; - isEsError: any; -} - -export declare function createRouter( - server: Legacy.Server, - pluginId: string, - apiBasePath: string, - config?: { - plugins: any[]; - } -): Router; - -export declare function isEsErrorFactory(server: Legacy.Server): any; diff --git a/x-pack/legacy/server/lib/create_router/index.js b/x-pack/legacy/server/lib/create_router/index.js deleted file mode 100644 index e4d66bdb5a48b..0000000000000 --- a/x-pack/legacy/server/lib/create_router/index.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import Boom from 'boom'; -import { callWithRequestFactory } from './call_with_request_factory'; -import { isEsErrorFactory as createIsEsError } from './is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from './error_wrappers'; -import { licensePreRoutingFactory } from './license_pre_routing_factory'; - -export { wrapEsError, wrapUnknownError, wrapCustomError } from './error_wrappers'; - -// Sometimes consumers will need to check if errors are ES errors, too. -export const isEsErrorFactory = (server) => { - return createIsEsError(server); -}; - -export const createRouter = (server, pluginId, apiBasePath = '', config) => { - const isEsError = isEsErrorFactory(server); - - // NOTE: The license-checking logic depends on the xpack_main plugin, so if your plugin - // consumes this helper, make sure it declares 'xpack_main' as a dependency. - const licensePreRouting = licensePreRoutingFactory(server, pluginId); - - const callWithRequestInstance = callWithRequestFactory(server, pluginId, config); - - const requestHandler = (handler) => async (request, h) => { - try { - const callWithRequest = (...args) => { - return callWithRequestInstance(request, ...args); - }; - return await handler(request, callWithRequest, h); - } catch (err) { - if (err instanceof Boom) { - throw err; - } - - if (isEsError(err)) { - throw wrapEsError(err); - } - - throw wrapUnknownError(err); - } - }; - - // Decorate base router with HTTP methods. - return ['get', 'post', 'put', 'delete', 'patch'].reduce((router, methodName) => { - router[methodName] = (subPath, handler) => { - const method = methodName.toUpperCase(); - const path = apiBasePath + subPath; - server.route({ - path, - method, - handler: requestHandler(handler), - config: { pre: [licensePreRouting] }, - }); - }; - return router; - }, {}); -}; diff --git a/x-pack/legacy/server/lib/create_router/is_es_error_factory/__tests__/is_es_error_factory.js b/x-pack/legacy/server/lib/create_router/is_es_error_factory/__tests__/is_es_error_factory.js deleted file mode 100644 index ef6fbaf9c53d0..0000000000000 --- a/x-pack/legacy/server/lib/create_router/is_es_error_factory/__tests__/is_es_error_factory.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { isEsErrorFactory } from '../is_es_error_factory'; -import { set } from '@elastic/safer-lodash-set'; - -class MockAbstractEsError {} - -describe('is_es_error_factory', () => { - let mockServer; - let isEsError; - - beforeEach(() => { - const mockEsErrors = { - _Abstract: MockAbstractEsError, - }; - mockServer = {}; - set(mockServer, 'plugins.elasticsearch.getCluster', () => ({ errors: mockEsErrors })); - - isEsError = isEsErrorFactory(mockServer); - }); - - describe('#isEsErrorFactory', () => { - it('should return a function', () => { - expect(isEsError).to.be.a(Function); - }); - - describe('returned function', () => { - it('should return true if passed-in err is a known esError', () => { - const knownEsError = new MockAbstractEsError(); - expect(isEsError(knownEsError)).to.be(true); - }); - - it('should return false if passed-in err is not a known esError', () => { - const unknownEsError = {}; - expect(isEsError(unknownEsError)).to.be(false); - }); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/is_es_error_factory/is_es_error_factory.js b/x-pack/legacy/server/lib/create_router/is_es_error_factory/is_es_error_factory.js deleted file mode 100644 index 80daac5bd496d..0000000000000 --- a/x-pack/legacy/server/lib/create_router/is_es_error_factory/is_es_error_factory.js +++ /dev/null @@ -1,18 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { memoize } from 'lodash'; - -const esErrorsFactory = memoize((server) => { - return server.plugins.elasticsearch.getCluster('admin').errors; -}); - -export function isEsErrorFactory(server) { - const esErrors = esErrorsFactory(server); - return function isEsError(err) { - return err instanceof esErrors._Abstract; - }; -} diff --git a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/__tests__/license_pre_routing_factory.js deleted file mode 100644 index dde18a0ccd7dd..0000000000000 --- a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/__tests__/license_pre_routing_factory.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { licensePreRoutingFactory } from '../license_pre_routing_factory'; -import { LICENSE_STATUS_INVALID, LICENSE_STATUS_VALID } from '../../../../../common/constants'; - -describe('license_pre_routing_factory', () => { - describe('#reportingFeaturePreRoutingFactory', () => { - let mockServer; - let mockLicenseCheckResults; - - beforeEach(() => { - mockServer = { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults, - }), - }, - }, - }, - }; - }); - - it('instantiates a new instance per plugin', () => { - const firstInstance = licensePreRoutingFactory(mockServer, 'foo'); - const secondInstance = licensePreRoutingFactory(mockServer, 'bar'); - - expect(firstInstance).to.not.be(secondInstance); - }); - - describe('status is invalid', () => { - beforeEach(() => { - mockLicenseCheckResults = { - status: LICENSE_STATUS_INVALID, - }; - }); - - it('replies with 403', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - expect(() => licensePreRouting(stubRequest)).to.throwException((response) => { - expect(response).to.be.an(Error); - expect(response.isBoom).to.be(true); - expect(response.output.statusCode).to.be(403); - }); - }); - }); - - describe('status is valid', () => { - beforeEach(() => { - mockLicenseCheckResults = { - status: LICENSE_STATUS_VALID, - }; - }); - - it('replies with nothing', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - const response = licensePreRouting(stubRequest); - expect(response).to.be(null); - }); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/index.js b/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/index.js deleted file mode 100644 index 0743e443955f4..0000000000000 --- a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/index.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { licensePreRoutingFactory } from './license_pre_routing_factory'; diff --git a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/license_pre_routing_factory.js deleted file mode 100644 index 81640ebb35ea9..0000000000000 --- a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/license_pre_routing_factory.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { wrapCustomError } from '../error_wrappers'; -import { LICENSE_STATUS_VALID } from '../../../../common/constants'; - -export const licensePreRoutingFactory = (server, pluginId) => { - return () => { - const xpackMainPlugin = server.plugins.xpack_main; - const licenseCheckResults = xpackMainPlugin.info.feature(pluginId).getLicenseCheckResults(); - - // Apps which don't have any license restrictions will return undefined license check results. - if (licenseCheckResults) { - if (licenseCheckResults.status !== LICENSE_STATUS_VALID) { - const error = new Error(licenseCheckResults.message); - const statusCode = 403; - throw wrapCustomError(error, statusCode); - } - } - - return null; - }; -}; diff --git a/x-pack/legacy/server/lib/key_case_converter.js b/x-pack/legacy/server/lib/key_case_converter.js deleted file mode 100644 index a2a5452b3a1d9..0000000000000 --- a/x-pack/legacy/server/lib/key_case_converter.js +++ /dev/null @@ -1,52 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; - -// Note: This function uses _.clone. This will clone objects created by constructors other than Object -// to plain Object objects. Uncloneable values such as functions, DOM nodes, Maps, Sets, and WeakMaps -// will be cloned to the empty object. -function convertKeysToSpecifiedCaseDeep(object, caseConversionFunction) { - // Base case - if (!(_.isPlainObject(object) || _.isArray(object))) { - return object; - } - - // Clone (so we don't modify the original object that was passed in) - let newObject; - if (Array.isArray(object)) { - newObject = object.slice(0); - } else { - newObject = _.clone(object); - - // Convert top-level keys - newObject = _.mapKeys(newObject, (value, key) => caseConversionFunction(key)); - } - - // Recursively convert nested object keys - _.forEach( - newObject, - (value, key) => (newObject[key] = convertKeysToSpecifiedCaseDeep(value, caseConversionFunction)) - ); - - return newObject; -} - -function validateObject(object) { - if (!(_.isPlainObject(object) || _.isArray(object))) { - throw new Error('Specified object should be an Object or Array'); - } -} - -export function convertKeysToSnakeCaseDeep(object) { - validateObject(object); - return convertKeysToSpecifiedCaseDeep(object, _.snakeCase); -} - -export function convertKeysToCamelCaseDeep(object) { - validateObject(object); - return convertKeysToSpecifiedCaseDeep(object, _.camelCase); -} diff --git a/x-pack/legacy/server/lib/parse_kibana_state.js b/x-pack/legacy/server/lib/parse_kibana_state.js deleted file mode 100644 index a6c9bfbb511c1..0000000000000 --- a/x-pack/legacy/server/lib/parse_kibana_state.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { set } from '@elastic/safer-lodash-set'; -import { isPlainObject, omit, get } from 'lodash'; -import rison from 'rison-node'; - -const stateTypeKeys = { - global: '_g', - app: '_a', -}; - -class KibanaState { - constructor(query, type = 'global') { - const propId = stateTypeKeys[type]; - if (!isPlainObject(query)) throw new TypeError('Query parameter must be an object'); - if (!propId) throw new TypeError(`Unknown state type: '${type}'`); - - const queryValue = query[propId]; - - this.exists = Boolean(queryValue); - this.state = queryValue ? rison.decode(queryValue) : {}; - this.type = type; - } - - removeProps(props) { - this.state = omit(this.state, props); - } - - get(prop, defVal) { - return get(this.state, prop, defVal); - } - - set(prop, val) { - return set(this.state, prop, val); - } - - toString() { - return rison.encode(this.state); - } - - toQuery() { - const index = stateTypeKeys[this.type]; - const output = {}; - output[index] = this.toString(); - return output; - } -} - -export function parseKibanaState(query, type) { - return new KibanaState(query, type); -} diff --git a/x-pack/legacy/server/lib/register_license_checker/index.d.ts b/x-pack/legacy/server/lib/register_license_checker/index.d.ts deleted file mode 100644 index 555008921df42..0000000000000 --- a/x-pack/legacy/server/lib/register_license_checker/index.d.ts +++ /dev/null @@ -1,15 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Legacy } from 'kibana'; -import { LicenseType } from '../../../common/constants'; - -export declare function registerLicenseChecker( - server: Legacy.Server, - pluginId: string, - pluginName: string, - minimumLicenseRequired: LicenseType -): void; diff --git a/x-pack/legacy/server/lib/register_license_checker/index.js b/x-pack/legacy/server/lib/register_license_checker/index.js deleted file mode 100644 index 7b0f97c38d129..0000000000000 --- a/x-pack/legacy/server/lib/register_license_checker/index.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerLicenseChecker } from './register_license_checker'; diff --git a/x-pack/legacy/server/lib/register_license_checker/register_license_checker.js b/x-pack/legacy/server/lib/register_license_checker/register_license_checker.js deleted file mode 100644 index 57cbe30c25cb2..0000000000000 --- a/x-pack/legacy/server/lib/register_license_checker/register_license_checker.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pairwise } from 'rxjs/operators'; - -import { ServiceStatusLevels } from '../../../../../src/core/server'; -import { checkLicense } from '../check_license'; - -export function registerLicenseChecker(server, pluginId, pluginName, minimumLicenseRequired) { - const xpackMainPlugin = server.plugins.xpack_main; - const subscription = server.newPlatform.setup.core.status.core$ - .pipe(pairwise()) - .subscribe(([coreLast, coreCurrent]) => { - if ( - !subscription.closed && - coreLast.elasticsearch.level !== ServiceStatusLevels.available && - coreCurrent.elasticsearch.level === ServiceStatusLevels.available - ) { - // Unsubscribe as soon as ES becomes available so this function only runs once - subscription.unsubscribe(); - - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info - .feature(pluginId) - .registerLicenseCheckResultsGenerator((xpackLicenseInfo) => { - return checkLicense(pluginName, minimumLicenseRequired, xpackLicenseInfo); - }); - } - }); -} diff --git a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.js b/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.js deleted file mode 100644 index 109dbbb20e35d..0000000000000 --- a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.js +++ /dev/null @@ -1,83 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import * as Rx from 'rxjs'; -import { catchError, mergeMap, map, switchMap, tap } from 'rxjs/operators'; - -export const RETRY_SCALE_DURATION = 100; -export const RETRY_DURATION_MAX = 10000; - -const calculateDuration = (i) => { - const duration = i * RETRY_SCALE_DURATION; - if (duration > RETRY_DURATION_MAX) { - return RETRY_DURATION_MAX; - } - - return duration; -}; - -// we can't use a retryWhen here, because we want to propagate the red status and then retry -const propagateRedStatusAndScaleRetry = () => { - let i = 0; - return (err, caught) => - Rx.concat( - Rx.of({ - state: 'red', - message: err.message, - }), - Rx.timer(calculateDuration(++i)).pipe(mergeMap(() => caught)) - ); -}; - -export function watchStatusAndLicenseToInitialize(xpackMainPlugin, downstreamPlugin, initialize) { - const xpackInfo = xpackMainPlugin.info; - const xpackInfoFeature = xpackInfo.feature(downstreamPlugin.id); - - const upstreamStatus = xpackMainPlugin.status; - const currentStatus$ = Rx.of({ - state: upstreamStatus.state, - message: upstreamStatus.message, - }); - const newStatus$ = Rx.fromEvent( - upstreamStatus, - 'change', - null, - (previousState, previousMsg, state, message) => { - return { - state, - message, - }; - } - ); - const status$ = Rx.merge(currentStatus$, newStatus$); - - const currentLicense$ = Rx.of(xpackInfoFeature.getLicenseCheckResults()); - const newLicense$ = Rx.fromEventPattern(xpackInfo.onLicenseInfoChange.bind(xpackInfo)).pipe( - map(() => xpackInfoFeature.getLicenseCheckResults()) - ); - const license$ = Rx.merge(currentLicense$, newLicense$); - - Rx.combineLatest(status$, license$) - .pipe( - map(([status, license]) => ({ status, license })), - switchMap(({ status, license }) => { - if (status.state !== 'green') { - return Rx.of({ state: status.state, message: status.message }); - } - - return Rx.defer(() => initialize(license)).pipe( - map(() => ({ - state: 'green', - message: 'Ready', - })), - catchError(propagateRedStatusAndScaleRetry()) - ); - }), - tap(({ state, message }) => { - downstreamPlugin.status[state](message); - }) - ) - .subscribe(); -} diff --git a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.test.js b/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.test.js deleted file mode 100644 index 33282b7591db7..0000000000000 --- a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.test.js +++ /dev/null @@ -1,301 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EventEmitter } from 'events'; -import { - watchStatusAndLicenseToInitialize, - RETRY_SCALE_DURATION, - RETRY_DURATION_MAX, -} from './watch_status_and_license_to_initialize'; - -const createMockXpackMainPluginAndFeature = (featureId) => { - const licenseChangeCallbacks = []; - - const mockFeature = { - getLicenseCheckResults: jest.fn(), - mock: { - triggerLicenseChange: () => { - for (const callback of licenseChangeCallbacks) { - callback(); - } - }, - setLicenseCheckResults: (value) => { - mockFeature.getLicenseCheckResults.mockReturnValue(value); - }, - }, - }; - - const mockXpackMainPlugin = { - info: { - onLicenseInfoChange: (callback) => { - licenseChangeCallbacks.push(callback); - }, - feature: (id) => { - if (id === featureId) { - return mockFeature; - } - throw new Error('Unexpected feature'); - }, - }, - status: new EventEmitter(), - mock: { - setStatus: (state, message) => { - mockXpackMainPlugin.status.state = state; - mockXpackMainPlugin.status.message = message; - mockXpackMainPlugin.status.emit('change', null, null, state, message); - }, - }, - }; - - return { mockXpackMainPlugin, mockFeature }; -}; - -const createMockDownstreamPlugin = (id) => { - const defaultImplementation = () => { - throw new Error('Not implemented'); - }; - return { - id, - status: { - disabled: jest.fn().mockImplementation(defaultImplementation), - yellow: jest.fn().mockImplementation(defaultImplementation), - green: jest.fn().mockImplementation(defaultImplementation), - red: jest.fn().mockImplementation(defaultImplementation), - }, - }; -}; - -const advanceRetry = async (initializeCount) => { - await Promise.resolve(); - let duration = initializeCount * RETRY_SCALE_DURATION; - if (duration > RETRY_DURATION_MAX) { - duration = RETRY_DURATION_MAX; - } - jest.advanceTimersByTime(duration); -}; - -['red', 'yellow', 'disabled'].forEach((state) => { - test(`mirrors ${state} immediately`, () => { - const pluginId = 'foo-plugin'; - const message = `${state} is now the state`; - const { mockXpackMainPlugin } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus(state, message); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn(); - downstreamPlugin.status[state].mockImplementation(() => {}); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); - - expect(initializeMock).not.toHaveBeenCalled(); - expect(downstreamPlugin.status[state]).toHaveBeenCalledTimes(1); - expect(downstreamPlugin.status[state]).toHaveBeenCalledWith(message); - }); -}); - -test(`calls initialize and doesn't immediately set downstream status when the initial status is green`, () => { - const pluginId = 'foo-plugin'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green', 'green is now the state'); - const licenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(licenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn().mockImplementation(() => new Promise(() => {})); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); - - expect(initializeMock).toHaveBeenCalledTimes(1); - expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults); - expect(downstreamPlugin.status.green).toHaveBeenCalledTimes(0); -}); - -test(`sets downstream plugin's status to green when initialize resolves`, (done) => { - const pluginId = 'foo-plugin'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green', 'green is now the state'); - const licenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(licenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn().mockImplementation(() => Promise.resolve()); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); - - expect(initializeMock).toHaveBeenCalledTimes(1); - expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults); - downstreamPlugin.status.green.mockImplementation((actualMessage) => { - expect(actualMessage).toBe('Ready'); - done(); - }); -}); - -test(`sets downstream plugin's status to red when initialize initially rejects, and continually polls initialize`, (done) => { - jest.useFakeTimers(); - - const pluginId = 'foo-plugin'; - const errorMessage = 'the error message'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green'); - const licenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(licenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - - let isRed = false; - let initializeCount = 0; - const initializeMock = jest.fn().mockImplementation(() => { - ++initializeCount; - - // on the second retry, ensure we already set the status to red - if (initializeCount === 2) { - expect(isRed).toBe(true); - } - - // this should theoretically continue indefinitely, but we only have so long to run the tests - if (initializeCount === 100) { - done(); - } - - // everytime this is called, we have to wait for a new promise to be resolved - // allowing the Promise the we return below to run, and then advance the timers - setImmediate(() => { - advanceRetry(initializeCount); - }); - return Promise.reject(new Error(errorMessage)); - }); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); - - expect(initializeMock).toHaveBeenCalledTimes(1); - expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults); - downstreamPlugin.status.red.mockImplementation((message) => { - isRed = true; - expect(message).toBe(errorMessage); - }); -}); - -test(`sets downstream plugin's status to green when initialize resolves after rejecting 10 times`, (done) => { - jest.useFakeTimers(); - - const pluginId = 'foo-plugin'; - const errorMessage = 'the error message'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green'); - const licenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(licenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - - let initializeCount = 0; - const initializeMock = jest.fn().mockImplementation(() => { - ++initializeCount; - - // everytime this is called, we have to wait for a new promise to be resolved - // allowing the Promise the we return below to run, and then advance the timers - setImmediate(() => { - advanceRetry(initializeCount); - }); - - if (initializeCount >= 10) { - return Promise.resolve(); - } - - return Promise.reject(new Error(errorMessage)); - }); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); - - expect(initializeMock).toHaveBeenCalledTimes(1); - expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults); - downstreamPlugin.status.red.mockImplementation((message) => { - expect(initializeCount).toBeLessThan(10); - expect(message).toBe(errorMessage); - }); - downstreamPlugin.status.green.mockImplementation((message) => { - expect(initializeCount).toBe(10); - expect(message).toBe('Ready'); - done(); - }); -}); - -test(`calls initialize twice when it gets a new license and the status is green`, (done) => { - const pluginId = 'foo-plugin'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green'); - const firstLicenseCheckResults = Symbol(); - const secondLicenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(firstLicenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn().mockImplementation(() => Promise.resolve()); - - let count = 0; - downstreamPlugin.status.green.mockImplementation((message) => { - expect(message).toBe('Ready'); - ++count; - if (count === 1) { - mockFeature.mock.setLicenseCheckResults(secondLicenseCheckResults); - mockFeature.mock.triggerLicenseChange(); - } - if (count === 2) { - expect(initializeMock).toHaveBeenCalledWith(firstLicenseCheckResults); - expect(initializeMock).toHaveBeenCalledWith(secondLicenseCheckResults); - expect(initializeMock).toHaveBeenCalledTimes(2); - done(); - } - }); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); -}); - -test(`doesn't call initialize twice when it gets a new license when the status isn't green`, (done) => { - const pluginId = 'foo-plugin'; - const redMessage = 'the red message'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green'); - const firstLicenseCheckResults = Symbol(); - const secondLicenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(firstLicenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn().mockImplementation(() => Promise.resolve()); - - downstreamPlugin.status.green.mockImplementation((message) => { - expect(message).toBe('Ready'); - mockXpackMainPlugin.mock.setStatus('red', redMessage); - mockFeature.mock.setLicenseCheckResults(secondLicenseCheckResults); - mockFeature.mock.triggerLicenseChange(); - }); - - downstreamPlugin.status.red.mockImplementation((message) => { - expect(message).toBe(redMessage); - expect(initializeMock).toHaveBeenCalledTimes(1); - expect(initializeMock).toHaveBeenCalledWith(firstLicenseCheckResults); - done(); - }); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); -}); - -test(`calls initialize twice when the status changes to green twice`, (done) => { - const pluginId = 'foo-plugin'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green'); - const licenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(licenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn().mockImplementation(() => Promise.resolve()); - - let count = 0; - downstreamPlugin.status.green.mockImplementation((message) => { - expect(message).toBe('Ready'); - ++count; - if (count === 1) { - mockXpackMainPlugin.mock.setStatus('green'); - } - if (count === 2) { - expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults); - expect(initializeMock).toHaveBeenCalledTimes(2); - done(); - } - }); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); -}); diff --git a/x-pack/legacy/server/lib/xpack_usage.js b/x-pack/legacy/server/lib/xpack_usage.js deleted file mode 100644 index 50b50ba18c37f..0000000000000 --- a/x-pack/legacy/server/lib/xpack_usage.js +++ /dev/null @@ -1,16 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export function xpackUsage(client) { - /* - * Get an object over the Usage API that as available/enabled data and some - * select metadata for each of the X-Pack UI plugins - */ - return client.transport.request({ - method: 'GET', - path: '/_xpack/usage', - }); -} diff --git a/x-pack/package.json b/x-pack/package.json index 0560b1bebe42b..984843e5b6360 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -30,7 +30,7 @@ }, "devDependencies": { "@cypress/webpack-preprocessor": "^4.1.0", - "@elastic/apm-rum-react": "^1.2.3", + "@elastic/apm-rum-react": "^1.2.4", "@elastic/maki": "6.3.0", "@kbn/dev-utils": "1.0.0", "@kbn/es": "1.0.0", @@ -50,9 +50,11 @@ "@storybook/addon-storyshots": "^5.3.19", "@storybook/react": "^5.3.19", "@storybook/theming": "^5.3.19", - "@testing-library/jest-dom": "^5.8.0", - "@testing-library/react": "^9.3.2", - "@testing-library/react-hooks": "^3.2.1", + "@testing-library/dom": "^7.24.2", + "@testing-library/jest-dom": "^5.11.4", + "@testing-library/react": "^11.0.4", + "@testing-library/react-hooks": "^3.4.1", + "@testing-library/user-event": "^12.1.6", "@turf/bbox": "6.0.1", "@turf/bbox-polygon": "6.0.1", "@turf/boolean-contains": "6.0.1", @@ -111,7 +113,7 @@ "@types/proper-lockfile": "^3.0.1", "@types/puppeteer": "^1.20.1", "@types/react": "^16.9.36", - "@types/react-beautiful-dnd": "^12.1.1", + "@types/react-beautiful-dnd": "^13.0.0", "@types/react-dom": "^16.9.8", "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", @@ -126,7 +128,8 @@ "@types/styled-components": "^5.1.0", "@types/supertest": "^2.0.5", "@types/tar-fs": "^1.16.1", - "@types/testing-library__jest-dom": "^5.7.0", + "@types/testing-library__jest-dom": "^5.9.2", + "@types/testing-library__react-hooks": "^3.4.0", "@types/tinycolor2": "^1.4.1", "@types/use-resize-observer": "^6.0.0", "@types/uuid": "^3.4.4", @@ -221,7 +224,7 @@ "proxyquire": "1.8.0", "re-resizable": "^6.1.1", "react-apollo": "^2.1.4", - "react-beautiful-dnd": "^12.2.0", + "react-beautiful-dnd": "^13.0.0", "react-docgen-typescript-loader": "^3.1.1", "react-dropzone": "^4.2.9", "react-fast-compare": "^2.0.4", @@ -274,7 +277,7 @@ "@babel/register": "^7.10.5", "@babel/runtime": "^7.11.2", "@elastic/datemath": "5.0.3", - "@elastic/ems-client": "7.9.3", + "@elastic/ems-client": "7.10.0", "@elastic/eui": "29.0.0", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", @@ -358,7 +361,7 @@ "proper-lockfile": "^3.2.0", "puid": "1.0.7", "puppeteer-core": "^1.19.0", - "query-string": "5.1.1", + "query-string": "^6.13.2", "raw-loader": "3.1.0", "react": "^16.12.0", "react-datetime": "^2.14.0", diff --git a/x-pack/plugins/actions/server/constants/plugin.ts b/x-pack/plugins/actions/server/constants/plugin.ts index 7d20eb6990247..b82464bd92a18 100644 --- a/x-pack/plugins/actions/server/constants/plugin.ts +++ b/x-pack/plugins/actions/server/constants/plugin.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/constants'; +import { LicenseType } from '../../../licensing/server'; export const PLUGIN = { ID: 'actions', - MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements + MINIMUM_LICENSE_REQUIRED: 'basic' as LicenseType, // TODO: supposed to be changed up on requirements // eslint-disable-next-line @typescript-eslint/no-explicit-any getI18nName: (i18n: any): string => i18n.translate('xpack.actions.appName', { diff --git a/x-pack/plugins/alerts/server/constants/plugin.ts b/x-pack/plugins/alerts/server/constants/plugin.ts index c180b68680841..4e1e0c59e0b48 100644 --- a/x-pack/plugins/alerts/server/constants/plugin.ts +++ b/x-pack/plugins/alerts/server/constants/plugin.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/constants'; +import { LicenseType } from '../../../licensing/server'; export const PLUGIN = { ID: 'alerts', - MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements + MINIMUM_LICENSE_REQUIRED: 'basic' as LicenseType, // TODO: supposed to be changed up on requirements // all plugins seem to use getI18nName with any // eslint-disable-next-line @typescript-eslint/no-explicit-any getI18nName: (i18n: any): string => diff --git a/x-pack/plugins/apm/common/service_health_status.ts b/x-pack/plugins/apm/common/service_health_status.ts index 468f06ab97af8..1d4bcfb3b0e07 100644 --- a/x-pack/plugins/apm/common/service_health_status.ts +++ b/x-pack/plugins/apm/common/service_health_status.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { ANOMALY_SEVERITY } from '../../ml/common'; -import { EuiTheme } from '../../../legacy/common/eui_styled_components'; +import { EuiTheme } from '../../xpack_legacy/common'; export enum ServiceHealthStatus { healthy = 'healthy', diff --git a/x-pack/plugins/apm/common/service_map.ts b/x-pack/plugins/apm/common/service_map.ts index 1dc4d598cd2ee..02456f9b2050f 100644 --- a/x-pack/plugins/apm/common/service_map.ts +++ b/x-pack/plugins/apm/common/service_map.ts @@ -22,15 +22,30 @@ export interface ServiceConnectionNode extends cytoscape.NodeDataDefinition { [SERVICE_ENVIRONMENT]: string | null; [AGENT_NAME]: string; serviceAnomalyStats?: ServiceAnomalyStats; + label?: string; } export interface ExternalConnectionNode extends cytoscape.NodeDataDefinition { [SPAN_DESTINATION_SERVICE_RESOURCE]: string; [SPAN_TYPE]: string; [SPAN_SUBTYPE]: string; + label?: string; } export type ConnectionNode = ServiceConnectionNode | ExternalConnectionNode; +export interface ConnectionEdge { + id: string; + source: ConnectionNode['id']; + target: ConnectionNode['id']; + label?: string; + bidirectional?: boolean; + isInverseEdge?: boolean; +} + +export interface ConnectionElement { + data: ConnectionNode | ConnectionEdge; +} + export interface Connection { source: ConnectionNode; destination: ConnectionNode; @@ -57,3 +72,22 @@ export const invalidLicenseMessage = i18n.translate( "In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data.", } ); + +const NONGROUPED_SPANS: Record = { + aws: ['servicename'], + cache: ['all'], + db: ['all'], + external: ['graphql', 'grpc', 'websocket'], + messaging: ['all'], + template: ['handlebars'], +}; + +export function isSpanGroupingSupported(type?: string, subtype?: string) { + if (!type || !(type in NONGROUPED_SPANS)) { + return true; + } + return !NONGROUPED_SPANS[type].some( + (nongroupedSubType) => + nongroupedSubType === 'all' || nongroupedSubType === subtype + ); +} diff --git a/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature b/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature index ac4188a598458..7b894b6ca7aac 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature +++ b/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature @@ -27,3 +27,11 @@ Feature: CSM Dashboard Given a user clicks the page load breakdown filter When the user selected the breakdown Then breakdown series should appear in chart + + Scenario: Search by url filter focus + When a user clicks inside url search field + Then it displays top pages in the suggestion popover + + Scenario: Search by url filter + When a user enters a query in url search field + Then it should filter results based on query diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts index 461e2960c5e02..28af4fd5d8a56 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts @@ -16,7 +16,7 @@ Given(`a user browses the APM UI application for RUM Data`, () => { const RANGE_FROM = 'now-24h'; const RANGE_TO = 'now'; loginAndWaitForPage( - `/app/csm`, + `/app/ux`, { from: RANGE_FROM, to: RANGE_TO, diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts new file mode 100644 index 0000000000000..3b5dd70065055 --- /dev/null +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { When, Then } from 'cypress-cucumber-preprocessor/steps'; +import { DEFAULT_TIMEOUT } from './csm_dashboard'; + +When(`a user clicks inside url search field`, () => { + // wait for all loading to finish + cy.get('kbnLoadingIndicator').should('not.be.visible'); + cy.get('.euiStat__title-isLoading').should('not.be.visible'); + cy.get('span[data-cy=csmUrlFilter]', DEFAULT_TIMEOUT).within(() => { + cy.get('input.euiFieldSearch').click(); + }); +}); + +Then(`it displays top pages in the suggestion popover`, () => { + cy.get('kbnLoadingIndicator').should('not.be.visible'); + + cy.get('div.euiPopover__panel-isOpen', DEFAULT_TIMEOUT).within(() => { + const listOfUrls = cy.get('li.euiSelectableListItem'); + listOfUrls.should('have.length', 5); + + const actualUrlsText = [ + 'http://opbeans-node:3000/dashboardPage views: 17Page load duration: 109 ms ', + 'http://opbeans-node:3000/ordersPage views: 14Page load duration: 72 ms', + ]; + + cy.get('li.euiSelectableListItem') + .eq(0) + .should('have.text', actualUrlsText[0]); + cy.get('li.euiSelectableListItem') + .eq(1) + .should('have.text', actualUrlsText[1]); + }); +}); + +When(`a user enters a query in url search field`, () => { + cy.get('kbnLoadingIndicator').should('not.be.visible'); + + cy.get('[data-cy=csmUrlFilter]').within(() => { + cy.get('input.euiSelectableSearch').type('cus'); + }); + + cy.get('kbnLoadingIndicator').should('not.be.visible'); +}); + +Then(`it should filter results based on query`, () => { + cy.get('kbnLoadingIndicator').should('not.be.visible'); + + cy.get('div.euiPopover__panel-isOpen', DEFAULT_TIMEOUT).within(() => { + const listOfUrls = cy.get('li.euiSelectableListItem'); + listOfUrls.should('have.length', 1); + + const actualUrlsText = [ + 'http://opbeans-node:3000/customersPage views: 10Page load duration: 76 ms ', + ]; + + cy.get('li.euiSelectableListItem') + .eq(0) + .should('have.text', actualUrlsText[0]); + }); +}); diff --git a/x-pack/plugins/apm/e2e/ingest-data/replay.js b/x-pack/plugins/apm/e2e/ingest-data/replay.js index 6bab95635f558..74c86b1b09ab4 100644 --- a/x-pack/plugins/apm/e2e/ingest-data/replay.js +++ b/x-pack/plugins/apm/e2e/ingest-data/replay.js @@ -70,34 +70,40 @@ function incrementSpinnerCount({ success }) { } let iterIndex = 0; +function setItemMetaAndHeaders(item) { + const headers = { + 'content-type': 'application/x-ndjson', + }; + + if (SECRET_TOKEN) { + headers.Authorization = `Bearer ${SECRET_TOKEN}`; + } + + if (item.url === '/intake/v2/rum/events') { + if (iterIndex === userAgents.length) { + // set some event agent to opbean + setRumAgent(item); + iterIndex = 0; + } + headers['User-Agent'] = userAgents[iterIndex]; + headers['X-Forwarded-For'] = userIps[iterIndex]; + iterIndex++; + } + return headers; +} + function setRumAgent(item) { - item.body = item.body.replace( - '"name":"client"', - '"name":"opbean-client-rum"' - ); + if (item.body) { + item.body = item.body.replace( + '"name":"client"', + '"name":"opbean-client-rum"' + ); + } } -async function insertItem(item) { +async function insertItem(item, headers) { try { const url = `${APM_SERVER_URL}${item.url}`; - const headers = { - 'content-type': 'application/x-ndjson', - }; - - if (item.url === '/intake/v2/rum/events') { - if (iterIndex === userAgents.length) { - // set some event agent to opbean - setRumAgent(item); - iterIndex = 0; - } - headers['User-Agent'] = userAgents[iterIndex]; - headers['X-Forwarded-For'] = userIps[iterIndex]; - iterIndex++; - } - - if (SECRET_TOKEN) { - headers.Authorization = `Bearer ${SECRET_TOKEN}`; - } await axios({ method: item.method, @@ -133,8 +139,9 @@ async function init() { await Promise.all( items.map(async (item) => { try { + const headers = setItemMetaAndHeaders(item); // retry 5 times with exponential backoff - await pRetry(() => limit(() => insertItem(item)), { + await pRetry(() => limit(() => insertItem(item, headers)), { retries: 5, }); incrementSpinnerCount({ success: true }); diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx index c63ec3700c877..5ebe14b663f56 100644 --- a/x-pack/plugins/apm/public/application/csmApp.tsx +++ b/x-pack/plugins/apm/public/application/csmApp.tsx @@ -20,7 +20,7 @@ import { import { APMRouteDefinition } from '../application/routes'; import { renderAsRedirectTo } from '../components/app/Main/route_config'; import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; -import { RumHome } from '../components/app/RumDashboard/RumHome'; +import { RumHome, UX_LABEL } from '../components/app/RumDashboard/RumHome'; import { ApmPluginContext } from '../context/ApmPluginContext'; import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext'; import { UrlParamsProvider } from '../context/UrlParamsContext'; @@ -39,8 +39,8 @@ export const rumRoutes: APMRouteDefinition[] = [ { exact: true, path: '/', - render: renderAsRedirectTo('/csm'), - breadcrumb: 'Client Side Monitoring', + render: renderAsRedirectTo('/ux'), + breadcrumb: UX_LABEL, }, ]; diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx index 4aa2d841e8deb..0d61ca8e39845 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -62,15 +62,6 @@ export function renderAsRedirectTo(to: string) { // If you provide an inline function to the component prop, you would create a // new component every render. This results in the existing component unmounting // and the new component mounting instead of just updating the existing component. -// -// This means you should use `render` if you're providing an inline function. -// However, the `ApmRoute` component from @elastic/apm-rum-react, only supports -// `component`, and will give you a large console warning if you use `render`. -// -// This warning cannot be turned off -// (see https://github.com/elastic/apm-agent-rum-js/issues/881) so while this is -// slightly more code, it provides better performance without causing console -// warnings to appear. function HomeServices() { return ; } @@ -153,7 +144,7 @@ export const routes: APMRouteDefinition[] = [ { exact: true, path: '/', - component: renderAsRedirectTo('/services'), + render: renderAsRedirectTo('/services'), breadcrumb: 'APM', }, { @@ -175,7 +166,7 @@ export const routes: APMRouteDefinition[] = [ { exact: true, path: '/settings', - component: renderAsRedirectTo('/settings/agent-configuration'), + render: renderAsRedirectTo('/settings/agent-configuration'), breadcrumb: i18n.translate('xpack.apm.breadcrumb.listSettingsTitle', { defaultMessage: 'Settings', }), @@ -219,7 +210,7 @@ export const routes: APMRouteDefinition[] = [ exact: true, path: '/services/:serviceName', breadcrumb: ({ match }) => match.params.serviceName, - component: (props: RouteComponentProps<{ serviceName: string }>) => + render: (props: RouteComponentProps<{ serviceName: string }>) => renderAsRedirectTo( `/services/${props.match.params.serviceName}/transactions` )(props), diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx index 21a162111bc79..ba3641cc4dadd 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx @@ -14,7 +14,7 @@ describe('routes', () => { it('redirects to /services', () => { const location = { hash: '', pathname: '/', search: '' }; expect( - (route as any).component({ location } as any).props.to.pathname + (route!.render!({ location } as any) as any).props.to.pathname ).toEqual('/services'); }); }); @@ -28,9 +28,7 @@ describe('routes', () => { search: '', }; - expect( - ((route as any).component({ location }) as any).props.to - ).toEqual({ + expect((route!.render!({ location } as any) as any).props.to).toEqual({ hash: '', pathname: '/services/opbeans-python/transactions/view', search: diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/VisitorBreakdownChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/VisitorBreakdownChart.tsx index 34fcf62178711..dea6525d4be5f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/VisitorBreakdownChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/VisitorBreakdownChart.tsx @@ -10,6 +10,7 @@ import { DARK_THEME, Datum, LIGHT_THEME, + PartialTheme, Partition, PartitionLayout, Settings, @@ -34,6 +35,12 @@ interface Props { loading: boolean; } +const theme: PartialTheme = { + legend: { + verticalWidth: 100, + }, +}; + export function VisitorBreakdownChart({ loading, options }: Props) { const [darkMode] = useUiSetting$('theme:darkMode'); @@ -42,13 +49,13 @@ export function VisitorBreakdownChart({ loading, options }: Props) { : EUI_CHARTS_THEME_LIGHT; return ( - + { @@ -31,13 +31,18 @@ export function ClientMetrics() { return callApmApi({ pathname: '/api/apm/rum/client-metrics', params: { - query: { start, end, uiFilters: JSON.stringify(uiFilters) }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, + }, }, }); } return Promise.resolve(null); }, - [start, end, uiFilters] + [start, end, uiFilters, searchTerm] ); const STAT_STYLE = { width: '240px' }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index f97db3b42eecb..c8e45d2b2f672 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -22,7 +22,7 @@ export interface PercentileRange { export function PageLoadDistribution() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const [percentileRange, setPercentileRange] = useState({ min: null, @@ -41,6 +41,7 @@ export function PageLoadDistribution() { start, end, uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, ...(percentileRange.min && percentileRange.max ? { minPercentile: String(percentileRange.min), @@ -53,7 +54,14 @@ export function PageLoadDistribution() { } return Promise.resolve(null); }, - [end, start, uiFilters, percentileRange.min, percentileRange.max] + [ + end, + start, + uiFilters, + percentileRange.min, + percentileRange.max, + searchTerm, + ] ); const onPercentileChange = (min: number, max: number) => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts index 814cf977c9569..d6a544333531f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts @@ -17,7 +17,7 @@ interface Props { export const useBreakdowns = ({ percentileRange, field, value }: Props) => { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const { min: minP, max: maxP } = percentileRange ?? {}; @@ -32,6 +32,7 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => { end, breakdown: value, uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, ...(minP && maxP ? { minPercentile: String(minP), @@ -43,6 +44,6 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => { }); } }, - [end, start, uiFilters, field, value, minP, maxP] + [end, start, uiFilters, field, value, minP, maxP, searchTerm] ); }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 2991f9a15f085..f2da0955412e7 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -16,7 +16,7 @@ import { BreakdownItem } from '../../../../../typings/ui_filters'; export function PageViewsTrend() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const [breakdown, setBreakdown] = useState(null); @@ -30,6 +30,7 @@ export function PageViewsTrend() { start, end, uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, ...(breakdown ? { breakdowns: JSON.stringify(breakdown), @@ -41,7 +42,7 @@ export function PageViewsTrend() { } return Promise.resolve(undefined); }, - [end, start, uiFilters, breakdown] + [end, start, uiFilters, breakdown, searchTerm] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx index 296c0d268d131..ddef5cd08e521 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx @@ -53,16 +53,18 @@ export function RumDashboard() { - - - + + + - - + + + + - - - + + + ); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx index 24da5e9ef3897..9abf792d7a0cf 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx @@ -10,6 +10,10 @@ import { i18n } from '@kbn/i18n'; import { RumOverview } from '../RumDashboard'; import { RumHeader } from './RumHeader'; +export const UX_LABEL = i18n.translate('xpack.apm.ux.title', { + defaultMessage: 'User Experience', +}); + export function RumHome() { return (
@@ -17,11 +21,7 @@ export function RumHome() { -

- {i18n.translate('xpack.apm.csm.title', { - defaultMessage: 'Client Side Monitoring', - })} -

+

{UX_LABEL}

diff --git a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/ServiceNameFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx similarity index 94% rename from x-pack/plugins/apm/public/components/shared/LocalUIFilters/ServiceNameFilter/index.tsx rename to x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx index cbf9ba009dce2..f10c9e888a193 100644 --- a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/ServiceNameFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx @@ -13,8 +13,8 @@ import { import { i18n } from '@kbn/i18n'; import React, { useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { fromQuery, toQuery } from '../../Links/url_helpers'; +import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; interface Props { serviceNames: string[]; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/RenderOption.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/RenderOption.tsx new file mode 100644 index 0000000000000..1a6f4e25fc315 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/RenderOption.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { ReactNode } from 'react'; +import classNames from 'classnames'; +import { EuiHighlight, EuiSelectableOption } from '@elastic/eui'; +import styled from 'styled-components'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; + +const StyledSpan = styled.span` + color: ${euiLightVars.euiColorSecondaryText}; + font-weight: 500; + :not(:last-of-type)::after { + content: '•'; + margin: 0 4px; + } +`; + +const StyledListSpan = styled.span` + display: block; + margin-top: 4px; + font-size: 12px; +`; +export type UrlOption = { + meta?: string[]; +} & EuiSelectableOption; + +export const formatOptions = (options: EuiSelectableOption[]) => { + return options.map((item: EuiSelectableOption) => ({ + title: item.label, + ...item, + className: classNames( + 'euiSelectableTemplateSitewide__listItem', + item.className + ), + })); +}; + +export function selectableRenderOptions( + option: UrlOption, + searchValue: string +) { + return ( + <> + + {option.label} + + {renderOptionMeta(option.meta)} + + ); +} + +function renderOptionMeta(meta?: string[]): ReactNode { + if (!meta || meta.length < 1) return; + return ( + + {meta.map((item: string) => ( + {item} + ))} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx new file mode 100644 index 0000000000000..298ec15b8480b --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FormEvent, useRef, useState } from 'react'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPopover, + EuiPopoverTitle, + EuiSelectable, + EuiSelectableMessage, +} from '@elastic/eui'; +import { + formatOptions, + selectableRenderOptions, + UrlOption, +} from './RenderOption'; +import { I18LABELS } from '../../translations'; + +interface Props { + data: { + items: UrlOption[]; + total?: number; + }; + loading: boolean; + onInputChange: (e: FormEvent) => void; + onTermChange: () => void; + onChange: (updatedOptions: UrlOption[]) => void; + searchValue: string; + onClose: () => void; +} + +export function SelectableUrlList({ + data, + loading, + onInputChange, + onTermChange, + onChange, + searchValue, + onClose, +}: Props) { + const [popoverIsOpen, setPopoverIsOpen] = useState(false); + const [popoverRef, setPopoverRef] = useState(null); + const [searchRef, setSearchRef] = useState(null); + + const titleRef = useRef(null); + + const searchOnFocus = (e: React.FocusEvent) => { + setPopoverIsOpen(true); + }; + + const onSearchInput = (e: React.FormEvent) => { + onInputChange(e); + setPopoverIsOpen(true); + }; + + const searchOnBlur = (e: React.FocusEvent) => { + if ( + !popoverRef?.contains(e.relatedTarget as HTMLElement) && + !popoverRef?.contains(titleRef.current as HTMLDivElement) + ) { + setPopoverIsOpen(false); + } + }; + + const formattedOptions = formatOptions(data.items ?? []); + + const closePopover = () => { + setPopoverIsOpen(false); + onClose(); + if (searchRef) { + searchRef.blur(); + } + }; + + const loadingMessage = ( + + +
+

{I18LABELS.loadingResults}

+
+ ); + + const emptyMessage = ( + +

{I18LABELS.noResults}

+
+ ); + + const titleText = searchValue + ? I18LABELS.getSearchResultsLabel(data?.total ?? 0) + : I18LABELS.topPages; + + function PopOverTitle() { + return ( + + + + {loading ? : titleText} + + + { + onTermChange(); + setPopoverIsOpen(false); + }} + > + {I18LABELS.matchThisQuery} + + + + + ); + } + + return ( + + {(list, search) => ( + +
+ + {list} +
+
+ )} +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx new file mode 100644 index 0000000000000..b88cf29740dcd --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiTitle } from '@elastic/eui'; +import useDebounce from 'react-use/lib/useDebounce'; +import React, { useEffect, useState, FormEvent, useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; +import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../../hooks/useFetcher'; +import { I18LABELS } from '../../translations'; +import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; +import { formatToSec } from '../../UXMetrics/KeyUXMetrics'; +import { SelectableUrlList } from './SelectableUrlList'; +import { UrlOption } from './RenderOption'; + +interface Props { + onChange: (value: string[]) => void; +} + +export function URLSearch({ onChange: onFilterChange }: Props) { + const history = useHistory(); + + const { urlParams, uiFilters } = useUrlParams(); + + const { start, end, serviceName } = urlParams; + const [searchValue, setSearchValue] = useState(''); + + const [debouncedValue, setDebouncedValue] = useState(''); + + useDebounce( + () => { + setSearchValue(debouncedValue); + }, + 250, + [debouncedValue] + ); + + const updateSearchTerm = useCallback( + (searchTermN: string) => { + const newLocation = { + ...history.location, + search: fromQuery({ + ...toQuery(history.location.search), + searchTerm: searchTermN, + }), + }; + history.push(newLocation); + }, + [history] + ); + + const [checkedUrls, setCheckedUrls] = useState([]); + + const { data, status } = useFetcher( + (callApmApi) => { + if (start && end && serviceName) { + const { transactionUrl, ...restFilters } = uiFilters; + + return callApmApi({ + pathname: '/api/apm/rum-client/url-search', + params: { + query: { + start, + end, + uiFilters: JSON.stringify(restFilters), + urlQuery: searchValue, + }, + }, + }); + } + return Promise.resolve(null); + }, + [start, end, serviceName, uiFilters, searchValue] + ); + + useEffect(() => { + setCheckedUrls(uiFilters.transactionUrl || []); + }, [uiFilters]); + + const onChange = (updatedOptions: UrlOption[]) => { + const clickedItems = updatedOptions.filter( + (option) => option.checked === 'on' + ); + + setCheckedUrls(clickedItems.map((item) => item.url)); + }; + + const items: UrlOption[] = (data?.items ?? []).map((item) => ({ + label: item.url, + key: item.url, + meta: [ + I18LABELS.pageViews + ': ' + item.count, + I18LABELS.pageLoadDuration + ': ' + formatToSec(item.pld), + ], + url: item.url, + checked: checkedUrls?.includes(item.url) ? 'on' : undefined, + })); + + const onInputChange = (e: FormEvent) => { + setDebouncedValue(e.currentTarget.value); + }; + + const isLoading = status !== 'success'; + + const onTermChange = () => { + updateSearchTerm(searchValue); + }; + + const onClose = () => { + onFilterChange(checkedUrls); + }; + + return ( + <> + +

{I18LABELS.url}

+
+ + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/UrlList.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/UrlList.tsx new file mode 100644 index 0000000000000..437c005db37b0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/UrlList.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiFlexGrid, EuiFlexItem, EuiBadge } from '@elastic/eui'; +import styled from 'styled-components'; +import { i18n } from '@kbn/i18n'; +import { px, truncate, unit } from '../../../../style/variables'; + +const BadgeText = styled.div` + display: inline-block; + ${truncate(px(unit * 12))}; + vertical-align: middle; +`; + +interface Props { + value: string[]; + onRemove: (val: string) => void; +} + +const formatUrlValue = (val: string) => { + const maxUrlToDisplay = 30; + const urlLength = val.length; + if (urlLength < maxUrlToDisplay) { + return val; + } + const urlObj = new URL(val); + if (urlObj.pathname === '/') { + return val; + } + const domainVal = urlObj.hostname; + const extraLength = urlLength - maxUrlToDisplay; + const extraDomain = domainVal.substring(0, extraLength); + + if (urlObj.pathname.length + 7 > maxUrlToDisplay) { + return val.replace(domainVal, '..'); + } + + return val.replace(extraDomain, '..'); +}; + +const removeFilterLabel = i18n.translate( + 'xpack.apm.uifilter.badge.removeFilter', + { defaultMessage: 'Remove filter' } +); + +export function UrlList({ onRemove, value }: Props) { + return ( + + {value.map((val) => ( + + { + onRemove(val); + }} + onClickAriaLabel={removeFilterLabel} + iconOnClick={() => { + onRemove(val); + }} + iconOnClickAriaLabel={removeFilterLabel} + iconType="cross" + iconSide="right" + > + {formatUrlValue(val)} + + + ))} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx new file mode 100644 index 0000000000000..9d3c8d012871f --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React, { useCallback, useMemo } from 'react'; +import { EuiSpacer, EuiBadge } from '@elastic/eui'; +import { useHistory } from 'react-router-dom'; +import { Projection } from '../../../../../common/projections'; +import { useLocalUIFilters } from '../../../../hooks/useLocalUIFilters'; +import { URLSearch } from './URLSearch'; +import { LocalUIFilters } from '../../../shared/LocalUIFilters'; +import { UrlList } from './UrlList'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; + +const removeSearchTermLabel = i18n.translate( + 'xpack.apm.uiFilter.url.removeSearchTerm', + { defaultMessage: 'Clear url query' } +); + +export function URLFilter() { + const history = useHistory(); + + const { + urlParams: { searchTerm }, + } = useUrlParams(); + + const localUIFiltersConfig = useMemo(() => { + const config: React.ComponentProps = { + filterNames: ['transactionUrl'], + projection: Projection.rumOverview, + }; + + return config; + }, []); + + const { filters, setFilterValue } = useLocalUIFilters({ + ...localUIFiltersConfig, + }); + + const updateSearchTerm = useCallback( + (searchTermN?: string) => { + const newLocation = { + ...history.location, + search: fromQuery({ + ...toQuery(history.location.search), + searchTerm: searchTermN, + }), + }; + history.push(newLocation); + }, + [history] + ); + + const { name, value: filterValue } = filters[0]; + + return ( + + + { + setFilterValue('transactionUrl', value); + }} + /> + + {searchTerm && ( + <> + { + updateSearchTerm(); + }} + onClickAriaLabel={removeSearchTermLabel} + iconOnClick={() => { + updateSearchTerm(); + }} + iconOnClickAriaLabel={removeSearchTermLabel} + iconType="cross" + iconSide="right" + > + *{searchTerm}* + + + + )} + {filterValue.length > 0 && ( + { + setFilterValue( + name, + filterValue.filter((v) => val !== v) + ); + }} + value={filterValue} + /> + )} + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx index 5c9a636adec8f..1d8360872afba 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx @@ -38,7 +38,7 @@ interface Props { export function KeyUXMetrics({ data, loading }: Props) { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, serviceName } = urlParams; + const { start, end, serviceName, searchTerm } = urlParams; const { data: longTaskData, status } = useFetcher( (callApmApi) => { @@ -46,13 +46,18 @@ export function KeyUXMetrics({ data, loading }: Props) { return callApmApi({ pathname: '/api/apm/rum-client/long-task-metrics', params: { - query: { start, end, uiFilters: JSON.stringify(uiFilters) }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, + }, }, }); } return Promise.resolve(null); }, - [start, end, serviceName, uiFilters] + [start, end, serviceName, uiFilters, searchTerm] ); // Note: FCP value is in ms unit diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx index 94c3acfaa9727..3c7b4e39401de 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx @@ -33,7 +33,7 @@ export interface UXMetrics { export function UXMetrics() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const { data, status } = useFetcher( (callApmApi) => { @@ -42,13 +42,18 @@ export function UXMetrics() { return callApmApi({ pathname: '/api/apm/rum-client/web-core-vitals', params: { - query: { start, end, uiFilters: JSON.stringify(uiFilters) }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, + }, }, }); } return Promise.resolve(null); }, - [start, end, uiFilters] + [start, end, uiFilters, searchTerm] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx index 245f58370d3d7..2db6ef8fa6c06 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx @@ -14,7 +14,7 @@ import { useUrlParams } from '../../../../hooks/useUrlParams'; export function VisitorBreakdown() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const { data, status } = useFetcher( (callApmApi) => { @@ -26,13 +26,14 @@ export function VisitorBreakdown() { start, end, uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, }, }, }); } return Promise.resolve(null); }, - [end, start, uiFilters] + [end, start, uiFilters, searchTerm] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx index fa0551252b6a1..588831d55771d 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx @@ -12,14 +12,15 @@ import { EuiSpacer, } from '@elastic/eui'; import { useTrackPageview } from '../../../../../observability/public'; -import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { Projection } from '../../../../common/projections'; import { RumDashboard } from './RumDashboard'; -import { ServiceNameFilter } from '../../shared/LocalUIFilters/ServiceNameFilter'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { useFetcher } from '../../../hooks/useFetcher'; import { RUM_AGENTS } from '../../../../common/agent_name'; import { EnvironmentFilter } from '../../shared/EnvironmentFilter'; +import { URLFilter } from './URLFilter'; +import { LocalUIFilters } from '../../shared/LocalUIFilters'; +import { ServiceNameFilter } from './URLFilter/ServiceNameFilter'; export function RumOverview() { useTrackPageview({ app: 'apm', path: 'rum_overview' }); @@ -27,7 +28,7 @@ export function RumOverview() { const localUIFiltersConfig = useMemo(() => { const config: React.ComponentProps = { - filterNames: ['transactionUrl', 'location', 'device', 'os', 'browser'], + filterNames: ['location', 'device', 'os', 'browser'], projection: Projection.rumOverview, }; @@ -63,6 +64,7 @@ export function RumOverview() { + <> + {' '} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts index 1fafb7d1ed4d0..714788ef468c6 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts @@ -79,6 +79,32 @@ export const I18LABELS = { defaultMessage: 'Page load duration by region', } ), + searchByUrl: i18n.translate('xpack.apm.rum.filters.searchByUrl', { + defaultMessage: 'Search by url', + }), + getSearchResultsLabel: (total: number) => + i18n.translate('xpack.apm.rum.filters.searchResults', { + defaultMessage: '{total} Search results', + values: { total }, + }), + topPages: i18n.translate('xpack.apm.rum.filters.topPages', { + defaultMessage: 'Top pages', + }), + select: i18n.translate('xpack.apm.rum.filters.select', { + defaultMessage: 'Select', + }), + url: i18n.translate('xpack.apm.rum.filters.url', { + defaultMessage: 'Url', + }), + matchThisQuery: i18n.translate('xpack.apm.rum.filters.url.matchThisQuery', { + defaultMessage: 'Match this query', + }), + loadingResults: i18n.translate('xpack.apm.rum.filters.url.loadingResults', { + defaultMessage: 'Loading results', + }), + noResults: i18n.translate('xpack.apm.rum.filters.url.noResults', { + defaultMessage: 'No results available', + }), }; export const VisitorBreakdownLabel = i18n.translate( diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 41dacfd8b588a..5fa74f927d2de 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -6,6 +6,8 @@ import cytoscape from 'cytoscape'; import dagre from 'cytoscape-dagre'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isEqual from 'lodash/isEqual'; import React, { createContext, diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx index 094cf032c4c9d..7771a232a5c9e 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx @@ -3,7 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import cytoscape from 'cytoscape'; import React from 'react'; @@ -12,20 +16,27 @@ import { SPAN_SUBTYPE, SPAN_TYPE, } from '../../../../../common/elasticsearch_fieldnames'; +import { ExternalConnectionNode } from '../../../../../common/service_map'; const ItemRow = styled.div` line-height: 2; `; -const ItemTitle = styled.dt` - color: ${({ theme }) => theme.eui.textColors.subdued}; +const SubduedDescriptionListTitle = styled(EuiDescriptionListTitle)` + &&& { + color: ${({ theme }) => theme.eui.textColors.subdued}; + } `; -const ItemDescription = styled.dd``; +const ExternalResourcesList = styled.section` + max-height: 360px; + overflow: auto; +`; interface InfoProps extends cytoscape.NodeDataDefinition { type?: string; subtype?: string; + className?: string; } export function Info(data: InfoProps) { @@ -51,15 +62,51 @@ export function Info(data: InfoProps) { }, ]; + if (data.groupedConnections) { + return ( + + + {data.groupedConnections.map((resource: ExternalConnectionNode) => { + const title = + resource.label || resource['span.destination.service.resource']; + const desc = `${resource['span.type']} (${resource['span.subtype']})`; + return ( + <> + + {title} + + + {desc} + + + ); + })} + + + ); + } + return ( <> {listItems.map( ({ title, description }) => description && ( - - {title} - {description} - +
+ + + {title} + + + {description} + + +
) )} diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 61ac9bd7cd54c..136be1c7d947c 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -136,7 +136,7 @@ const getStyle = (theme: EuiTheme): cytoscape.Stylesheet[] => { label: (el: cytoscape.NodeSingular) => isService(el) ? el.data(SERVICE_NAME) - : el.data(SPAN_DESTINATION_SERVICE_RESOURCE), + : el.data('label') || el.data(SPAN_DESTINATION_SERVICE_RESOURCE), 'min-zoomed-font-size': parseInt(theme.eui.euiSizeS, 10), 'overlay-opacity': 0, shape: (el: cytoscape.NodeSingular) => diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.ts index 3f879196f2a4f..e77fc4dfb2f51 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.ts @@ -5,6 +5,8 @@ */ import cytoscape from 'cytoscape'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import debounce from 'lodash/debounce'; import { useEffect } from 'react'; import { EuiTheme, useUiTracker } from '../../../../../observability/public'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/ClientSideMonitoringCallout.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/ClientSideMonitoringCallout.tsx index b6938b211994d..becae4d7eb5d7 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/ClientSideMonitoringCallout.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/ClientSideMonitoringCallout.tsx @@ -11,14 +11,14 @@ import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; export function ClientSideMonitoringCallout() { const { core } = useApmPluginContext(); - const clientSideMonitoringHref = core.http.basePath.prepend(`/app/csm`); + const clientSideMonitoringHref = core.http.basePath.prepend(`/app/ux`); return ( diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx b/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx index 2db4659c83603..59dd9455c724c 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx @@ -4,17 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, RenderHookResult } from '@testing-library/react-hooks'; import { delay } from '../utils/testHelpers'; -import { useFetcher } from './useFetcher'; +import { FetcherResult, useFetcher } from './useFetcher'; import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; +import { ApmPluginContextValue } from '../context/ApmPluginContext'; // Wrap the hook with a provider so it can useApmPluginContext const wrapper = MockApmPluginContextWrapper; describe('useFetcher', () => { describe('when resolving after 500ms', () => { - let hook: ReturnType; + let hook: RenderHookResult< + { children?: React.ReactNode; value?: ApmPluginContextValue }, + FetcherResult & { + refetch: () => void; + } + >; beforeEach(() => { jest.useFakeTimers(); async function fn() { @@ -58,7 +64,12 @@ describe('useFetcher', () => { }); describe('when throwing after 500ms', () => { - let hook: ReturnType; + let hook: RenderHookResult< + { children?: React.ReactNode; value?: ApmPluginContextValue }, + FetcherResult & { + refetch: () => void; + } + >; beforeEach(() => { jest.useFakeTimers(); async function fn() { diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index ab3f1026a92dd..dd9659a4cd1be 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -120,8 +120,8 @@ export class ApmPlugin implements Plugin { }); core.application.register({ - id: 'csm', - title: 'Client Side Monitoring', + id: 'ux', + title: 'User Experience', order: 8500, euiIconType: 'logoObservability', category: DEFAULT_APP_CATEGORIES.observability, diff --git a/x-pack/plugins/apm/server/feature.ts b/x-pack/plugins/apm/server/feature.ts index 14d8e2c3a4d50..75d8842d4843b 100644 --- a/x-pack/plugins/apm/server/feature.ts +++ b/x-pack/plugins/apm/server/feature.ts @@ -16,13 +16,13 @@ import { export const APM_FEATURE = { id: 'apm', name: i18n.translate('xpack.apm.featureRegistry.apmFeatureName', { - defaultMessage: 'APM and Client Side Monitoring', + defaultMessage: 'APM and User Experience', }), order: 900, category: DEFAULT_APP_CATEGORIES.observability, icon: 'apmApp', navLinkId: 'apm', - app: ['apm', 'csm', 'kibana'], + app: ['apm', 'ux', 'kibana'], catalogue: ['apm'], management: { insightsAndAlerting: ['triggersActions'], @@ -31,7 +31,7 @@ export const APM_FEATURE = { // see x-pack/plugins/features/common/feature_kibana_privileges.ts privileges: { all: { - app: ['apm', 'csm', 'kibana'], + app: ['apm', 'ux', 'kibana'], api: ['apm', 'apm_write'], catalogue: ['apm'], savedObject: { @@ -47,7 +47,7 @@ export const APM_FEATURE = { ui: ['show', 'save', 'alerting:show', 'alerting:save'], }, read: { - app: ['apm', 'csm', 'kibana'], + app: ['apm', 'ux', 'kibana'], api: ['apm'], catalogue: ['apm'], savedObject: { diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts index cb30c6c064848..49030dc8cacc5 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts @@ -8,7 +8,7 @@ import LRU from 'lru-cache'; import { LegacyAPICaller } from '../../../../../../src/core/server'; import { IndexPatternsFetcher, - IIndexPattern, + FieldDescriptor, } from '../../../../../../src/plugins/data/server'; import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; import { @@ -17,7 +17,12 @@ import { } from '../../../common/processor_event'; import { APMRequestHandlerContext } from '../../routes/typings'; -const cache = new LRU({ +interface IndexPatternTitleAndFields { + title: string; + fields: FieldDescriptor[]; +} + +const cache = new LRU({ max: 100, maxAge: 1000 * 60, }); @@ -53,7 +58,7 @@ export const getDynamicIndexPattern = async ({ pattern: patternIndices, }); - const indexPattern: IIndexPattern = { + const indexPattern: IndexPatternTitleAndFields = { fields, title: indexPatternTitle, }; diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts index b3f9646f64029..cf4a5538a208d 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts @@ -19,11 +19,14 @@ import { export async function getClientMetrics({ setup, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ setup, + urlQuery, }); const params = mergeProjection(projection, { diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts b/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts index 1faee52034580..812cf9865bda8 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts @@ -14,12 +14,17 @@ import { SetupTimeRange, SetupUIFilters, } from '../helpers/setup_request'; -import { SPAN_DURATION } from '../../../common/elasticsearch_fieldnames'; +import { + SPAN_DURATION, + TRANSACTION_ID, +} from '../../../common/elasticsearch_fieldnames'; export async function getLongTaskMetrics({ setup, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; }) { const projection = getRumLongTasksProjection({ setup, @@ -28,9 +33,6 @@ export async function getLongTaskMetrics({ const params = mergeProjection(projection, { body: { size: 0, - query: { - bool: projection.body.query.bool, - }, aggs: { transIds: { terms: { @@ -59,10 +61,13 @@ export async function getLongTaskMetrics({ const response = await apmEventClient.search(params); const { transIds } = response.aggregations ?? {}; - const validTransactions: string[] = await filterPageLoadTransactions( + const validTransactions: string[] = await filterPageLoadTransactions({ setup, - (transIds?.buckets ?? []).map((bucket) => bucket.key as string) - ); + urlQuery, + transactionIds: (transIds?.buckets ?? []).map( + (bucket) => bucket.key as string + ), + }); let noOfLongTasks = 0; let sumOfLongTasks = 0; let longestLongTask = 0; @@ -83,12 +88,18 @@ export async function getLongTaskMetrics({ }; } -async function filterPageLoadTransactions( - setup: Setup & SetupTimeRange & SetupUIFilters, - transactionIds: string[] -) { +async function filterPageLoadTransactions({ + setup, + urlQuery, + transactionIds, +}: { + setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; + transactionIds: string[]; +}) { const projection = getRumPageLoadTransactionsProjection({ setup, + urlQuery, }); const params = mergeProjection(projection, { @@ -99,14 +110,14 @@ async function filterPageLoadTransactions( must: [ { terms: { - 'transaction.id': transactionIds, + [TRANSACTION_ID]: transactionIds, }, }, ], filter: [...projection.body.query.bool.filter], }, }, - _source: ['transaction.id'], + _source: [TRANSACTION_ID], }, }); diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts index 3d8ab7a72654d..25de9f06fefc4 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts @@ -40,13 +40,16 @@ export async function getPageLoadDistribution({ setup, minPercentile, maxPercentile, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; minPercentile?: string; maxPercentile?: string; + urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ setup, + urlQuery, }); const params = mergeProjection(projection, { diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts index 543aa911b0b1f..352a3ecdc3f12 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts @@ -18,6 +18,7 @@ export async function getPageViewTrends({ }: { setup: Setup & SetupTimeRange & SetupUIFilters; breakdowns?: string; + urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ setup, @@ -45,7 +46,7 @@ export async function getPageViewTrends({ terms: { field: breakdownItem.fieldName, size: 9, - missing: 'Other', + missing: 'Others', }, }, } @@ -102,7 +103,7 @@ export async function getPageViewTrends({ }); // Top 9 plus others, get a diff from parent bucket total if (bCount > top9Count) { - res.Other = bCount - top9Count; + res.Others = bCount - top9Count; } } diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts index 1945140e35777..d59817cc682a9 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts @@ -44,11 +44,13 @@ export const getPageLoadDistBreakdown = async ({ minDuration, maxDuration, breakdown, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; minDuration: number; maxDuration: number; breakdown: string; + urlQuery?: string; }) => { // convert secs to micros const stepValues = getPLDChartSteps({ @@ -58,6 +60,7 @@ export const getPageLoadDistBreakdown = async ({ const projection = getRumPageLoadTransactionsProjection({ setup, + urlQuery, }); const params = mergeProjection(projection, { diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts b/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts new file mode 100644 index 0000000000000..a7117f275c17b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mergeProjection } from '../../projections/util/merge_projection'; +import { + Setup, + SetupTimeRange, + SetupUIFilters, +} from '../helpers/setup_request'; +import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; + +export async function getUrlSearch({ + setup, + urlQuery, +}: { + setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; +}) { + const projection = getRumPageLoadTransactionsProjection({ + setup, + urlQuery, + }); + + const params = mergeProjection(projection, { + body: { + size: 0, + aggs: { + totalUrls: { + cardinality: { + field: 'url.full', + }, + }, + urls: { + terms: { + field: 'url.full', + size: 10, + }, + aggs: { + medianPLD: { + percentiles: { + field: 'transaction.duration.us', + percents: [50], + }, + }, + }, + }, + }, + }, + }); + + const { apmEventClient } = setup; + + const response = await apmEventClient.search(params); + const { urls, totalUrls } = response.aggregations ?? {}; + + return { + total: totalUrls?.value || 0, + items: (urls?.buckets ?? []).map((bucket) => ({ + url: bucket.key as string, + count: bucket.doc_count, + pld: bucket.medianPLD.values['50.0'] ?? 0, + })), + }; +} diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts b/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts index 3493307929f42..7345d6acc0f82 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts @@ -12,23 +12,26 @@ import { SetupUIFilters, } from '../helpers/setup_request'; import { - USER_AGENT_DEVICE, USER_AGENT_NAME, USER_AGENT_OS, } from '../../../common/elasticsearch_fieldnames'; export async function getVisitorBreakdown({ setup, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ setup, + urlQuery, }); const params = mergeProjection(projection, { body: { size: 0, + track_total_hits: true, query: { bool: projection.body.query.bool, }, @@ -36,19 +39,13 @@ export async function getVisitorBreakdown({ browsers: { terms: { field: USER_AGENT_NAME, - size: 10, + size: 9, }, }, os: { terms: { field: USER_AGENT_OS, - size: 10, - }, - }, - devices: { - terms: { - field: USER_AGENT_DEVICE, - size: 10, + size: 9, }, }, }, @@ -58,20 +55,42 @@ export async function getVisitorBreakdown({ const { apmEventClient } = setup; const response = await apmEventClient.search(params); - const { browsers, os, devices } = response.aggregations!; + const { browsers, os } = response.aggregations!; + + const totalItems = response.hits.total.value; + + const browserTotal = browsers.buckets.reduce( + (prevVal, item) => prevVal + item.doc_count, + 0 + ); + + const osTotal = os.buckets.reduce( + (prevVal, item) => prevVal + item.doc_count, + 0 + ); + + const browserItems = browsers.buckets.map((bucket) => ({ + count: bucket.doc_count, + name: bucket.key as string, + })); + + browserItems.push({ + count: totalItems - browserTotal, + name: 'Others', + }); + + const osItems = os.buckets.map((bucket) => ({ + count: bucket.doc_count, + name: bucket.key as string, + })); + + osItems.push({ + count: totalItems - osTotal, + name: 'Others', + }); return { - browsers: browsers.buckets.map((bucket) => ({ - count: bucket.doc_count, - name: bucket.key as string, - })), - os: os.buckets.map((bucket) => ({ - count: bucket.doc_count, - name: bucket.key as string, - })), - devices: devices.buckets.map((bucket) => ({ - count: bucket.doc_count, - name: bucket.key as string, - })), + os: osItems, + browsers: browserItems, }; } diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts index 2ff0173b9ac12..fa34c2e25fecd 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts @@ -22,8 +22,10 @@ import { export async function getWebCoreVitals({ setup, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ setup, diff --git a/x-pack/plugins/apm/server/lib/service_map/group_resource_nodes.test.ts b/x-pack/plugins/apm/server/lib/service_map/group_resource_nodes.test.ts new file mode 100644 index 0000000000000..2a9a2daf1fe47 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/service_map/group_resource_nodes.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { groupResourceNodes } from './group_resource_nodes'; +import preGroupedData from './mock_responses/group_resource_nodes_pregrouped.json'; +import expectedGroupedData from './mock_responses/group_resource_nodes_grouped.json'; + +describe('groupResourceNodes', () => { + it('should group external nodes', () => { + const responseWithGroups = groupResourceNodes(preGroupedData); + expect(responseWithGroups.elements).toHaveLength( + expectedGroupedData.elements.length + ); + for (const element of responseWithGroups.elements) { + const expectedElement = expectedGroupedData.elements.find( + ({ data: { id } }: { data: { id: string } }) => id === element.data.id + ); + expect(element).toMatchObject(expectedElement); + } + }); +}); diff --git a/x-pack/plugins/apm/server/lib/service_map/group_resource_nodes.ts b/x-pack/plugins/apm/server/lib/service_map/group_resource_nodes.ts new file mode 100644 index 0000000000000..37ddcdfcff719 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/service_map/group_resource_nodes.ts @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import { groupBy } from 'lodash'; +import { ValuesType } from 'utility-types'; +import { + SPAN_TYPE, + SPAN_SUBTYPE, +} from '../../../common/elasticsearch_fieldnames'; +import { + ConnectionElement, + isSpanGroupingSupported, +} from '../../../common/service_map'; + +const MINIMUM_GROUP_SIZE = 4; + +export function groupResourceNodes(responseData: { + elements: ConnectionElement[]; +}) { + type ElementDefinition = ValuesType; + const isEdge = (el: ElementDefinition) => + Boolean(el.data.source && el.data.target); + const isNode = (el: ElementDefinition) => !isEdge(el); + const isElligibleGroupNode = (el: ElementDefinition) => { + if (isNode(el) && 'span.type' in el.data) { + return isSpanGroupingSupported(el.data[SPAN_TYPE], el.data[SPAN_SUBTYPE]); + } + return false; + }; + const nodes = responseData.elements.filter(isNode); + const edges = responseData.elements.filter(isEdge); + + // create adjacency list by targets + const groupNodeCandidates = responseData.elements + .filter(isElligibleGroupNode) + .map(({ data: { id } }) => id); + const adjacencyListByTargetMap = new Map(); + edges.forEach(({ data: { source, target } }) => { + if (groupNodeCandidates.includes(target)) { + const sources = adjacencyListByTargetMap.get(target); + if (sources) { + sources.push(source); + } else { + adjacencyListByTargetMap.set(target, [source]); + } + } + }); + const adjacencyListByTarget = [...adjacencyListByTargetMap.entries()].map( + ([target, sources]) => ({ + target, + sources, + groupId: `resourceGroup{${sources.sort().join(';')}}`, + }) + ); + + // group by members + const nodeGroupsById = groupBy(adjacencyListByTarget, 'groupId'); + const nodeGroups = Object.keys(nodeGroupsById) + .map((id) => ({ + id, + sources: nodeGroupsById[id][0].sources, + targets: nodeGroupsById[id].map(({ target }) => target), + })) + .filter(({ targets }) => targets.length > MINIMUM_GROUP_SIZE - 1); + const ungroupedEdges = [...edges]; + const ungroupedNodes = [...nodes]; + nodeGroups.forEach(({ sources, targets }) => { + targets.forEach((target) => { + // removes grouped nodes from original node set: + const groupedNodeIndex = ungroupedNodes.findIndex( + ({ data }) => data.id === target + ); + ungroupedNodes.splice(groupedNodeIndex, 1); + sources.forEach((source) => { + // removes edges of grouped nodes from original edge set: + const groupedEdgeIndex = ungroupedEdges.findIndex( + ({ data }) => data.source === source && data.target === target + ); + ungroupedEdges.splice(groupedEdgeIndex, 1); + }); + }); + }); + + // add in a composite node for each new group + const groupedNodes = nodeGroups.map(({ id, targets }) => ({ + data: { + id, + 'span.type': 'external', + label: i18n.translate('xpack.apm.serviceMap.resourceCountLabel', { + defaultMessage: '{count} resources', + values: { count: targets.length }, + }), + groupedConnections: targets + .map((targetId) => { + const targetElement = nodes.find( + (element) => element.data.id === targetId + ); + if (!targetElement) { + return; + } + const { data } = targetElement; + return { label: data.label || data.id, ...data }; + }) + .filter((node) => !!node), + }, + })); + + // add new edges from source to new groups + const groupedEdges: Array<{ + data: { + id: string; + source: string; + target: string; + }; + }> = []; + nodeGroups.forEach(({ id, sources }) => { + sources.forEach((source) => { + groupedEdges.push({ + data: { + id: `${source}~>${id}`, + source, + target: id, + }, + }); + }); + }); + + return { + elements: [ + ...ungroupedNodes, + ...groupedNodes, + ...ungroupedEdges, + ...groupedEdges, + ], + }; +} diff --git a/x-pack/plugins/apm/server/lib/service_map/mock_responses/group_resource_nodes_grouped.json b/x-pack/plugins/apm/server/lib/service_map/mock_responses/group_resource_nodes_grouped.json new file mode 100644 index 0000000000000..e7bba585de180 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/service_map/mock_responses/group_resource_nodes_grouped.json @@ -0,0 +1,140 @@ +{ + "elements": [ + { + "data": { + "id": "opbeans-rum", + "service.environment": "testing", + "service.name": "opbeans-rum", + "agent.name": "rum-js" + } + }, + { + "data": { + "source": "opbeans-rum", + "target": "opbeans-node", + "id": "opbeans-rum~>opbeans-node" + } + }, + { + "data": { + "id": "opbeans-node", + "service.environment": "testing", + "service.name": "opbeans-node", + "agent.name": "nodejs" + } + }, + { + "data": { + "source": "opbeans-node", + "target": "postgresql", + "id": "opbeans-node~>postgresql" + } + }, + { + "data": { + "id": "postgresql", + "span.subtype": "postgresql", + "span.destination.service.resource": "postgresql", + "span.type": "db", + "label": "postgresql" + } + }, + { + "data": { + "id": "elastic-co-rum-test", + "service.name": "elastic-co-rum-test", + "agent.name": "rum-js" + } + }, + { + "data": { + "id": "elastic-co-frontend", + "service.name": "elastic-co-frontend", + "agent.name": "rum-js" + } + }, + { + "data": { + "source": "elastic-co-frontend", + "target": "0.cdn.example.com:443", + "id": "elastic-co-frontend~>0.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-frontend", + "target": "resourceGroup{elastic-co-frontend;elastic-co-rum-test}", + "id": "elastic-co-frontend~>resourceGroup{elastic-co-frontend;elastic-co-rum-test}" + } + }, + { + "data": { + "source": "elastic-co-rum-test", + "target": "resourceGroup{elastic-co-frontend;elastic-co-rum-test}", + "id": "elastic-co-rum-test~>resourceGroup{elastic-co-frontend;elastic-co-rum-test}" + } + }, + { + "data": { + "source": "elastic-co-rum-test", + "target": "6.cdn.example.com:443", + "id": "elastic-co-rum-test~>6.cdn.example.com:443" + } + }, + { + "data": { + "id": "resourceGroup{elastic-co-frontend;elastic-co-rum-test}", + "span.type": "external", + "label": "5 resources", + "groupedConnections": [ + { + "label": "1.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "1.cdn.example.com:443" + }, + { + "label": "2.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "2.cdn.example.com:443" + }, + { + "label": "3.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "3.cdn.example.com:443" + }, + { + "label": "4.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "4.cdn.example.com:443" + }, + { + "label": "5.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "5.cdn.example.com:443" + } + ] + } + }, + { + "data": { + "id": "0.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "0.cdn.example.com:443" + } + }, + { + "data": { + "id": "6.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "6.cdn.example.com:443" + } + } + ] +} diff --git a/x-pack/plugins/apm/server/lib/service_map/mock_responses/group_resource_nodes_pregrouped.json b/x-pack/plugins/apm/server/lib/service_map/mock_responses/group_resource_nodes_pregrouped.json new file mode 100644 index 0000000000000..22c5c50de7472 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/service_map/mock_responses/group_resource_nodes_pregrouped.json @@ -0,0 +1,204 @@ +{ + "elements": [ + { + "data": { + "id": "opbeans-rum", + "service.environment": "testing", + "service.name": "opbeans-rum", + "agent.name": "rum-js" + } + }, + { + "data": { + "source": "opbeans-rum", + "target": "opbeans-node", + "id": "opbeans-rum~>opbeans-node" + } + }, + { + "data": { + "id": "opbeans-node", + "service.environment": "testing", + "service.name": "opbeans-node", + "agent.name": "nodejs" + } + }, + { + "data": { + "source": "opbeans-node", + "target": "postgresql", + "id": "opbeans-node~>postgresql" + } + }, + { + "data": { + "id": "postgresql", + "span.subtype": "postgresql", + "span.destination.service.resource": "postgresql", + "span.type": "db", + "label": "postgresql" + } + }, + { + "data": { + "id": "elastic-co-rum-test", + "service.name": "elastic-co-rum-test", + "agent.name": "rum-js" + } + }, + { + "data": { + "id": "elastic-co-frontend", + "service.name": "elastic-co-frontend", + "agent.name": "rum-js" + } + }, + { + "data": { + "source": "elastic-co-frontend", + "target": "0.cdn.example.com:443", + "id": "elastic-co-frontend~>0.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-frontend", + "target": "1.cdn.example.com:443", + "id": "elastic-co-frontend~>1.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-frontend", + "target": "2.cdn.example.com:443", + "id": "elastic-co-frontend~>2.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-frontend", + "target": "3.cdn.example.com:443", + "id": "elastic-co-frontend~>3.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-frontend", + "target": "4.cdn.example.com:443", + "id": "elastic-co-frontend~>4.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-frontend", + "target": "5.cdn.example.com:443", + "id": "elastic-co-frontend~>5.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-rum-test", + "target": "1.cdn.example.com:443", + "id": "elastic-co-rum-test~>1.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-rum-test", + "target": "2.cdn.example.com:443", + "id": "elastic-co-rum-test~>2.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-rum-test", + "target": "3.cdn.example.com:443", + "id": "elastic-co-rum-test~>3.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-rum-test", + "target": "4.cdn.example.com:443", + "id": "elastic-co-rum-test~>4.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-rum-test", + "target": "5.cdn.example.com:443", + "id": "elastic-co-rum-test~>5.cdn.example.com:443" + } + }, + { + "data": { + "source": "elastic-co-rum-test", + "target": "6.cdn.example.com:443", + "id": "elastic-co-rum-test~>6.cdn.example.com:443" + } + }, + { + "data": { + "id": "0.cdn.example.com:443", + "label": "0.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "0.cdn.example.com:443" + } + }, + { + "data": { + "id": "1.cdn.example.com:443", + "label": "1.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "1.cdn.example.com:443" + } + }, + { + "data": { + "id": "2.cdn.example.com:443", + "label": "2.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "2.cdn.example.com:443" + } + }, + { + "data": { + "id": "3.cdn.example.com:443", + "label": "3.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "3.cdn.example.com:443" + } + }, + { + "data": { + "id": "4.cdn.example.com:443", + "label": "4.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "4.cdn.example.com:443" + } + }, + { + "data": { + "id": "5.cdn.example.com:443", + "label": "5.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "5.cdn.example.com:443" + } + }, + { + "data": { + "id": "6.cdn.example.com:443", + "label": "6.cdn.example.com:443", + "span.type": "external", + "span.subtype": "http", + "span.destination.service.resource": "6.cdn.example.com:443" + } + } + ] +} diff --git a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts index f30b80feda302..7d832c91022e5 100644 --- a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts +++ b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts @@ -75,7 +75,11 @@ describe('transformServiceMapResponses', () => { (element) => 'source' in element.data && 'target' in element.data ); - expect(connection?.data.target).toBe('opbeans-node'); + expect(connection).toHaveProperty('data'); + expect(connection?.data).toHaveProperty('target'); + if (connection?.data && 'target' in connection.data) { + expect(connection.data.target).toBe('opbeans-node'); + } expect( elements.find((element) => element.data.id === '>opbeans-node') diff --git a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts index 7f5e34f68f922..e2af4315e41a1 100644 --- a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts +++ b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts @@ -16,9 +16,11 @@ import { ConnectionNode, ServiceConnectionNode, ExternalConnectionNode, + ConnectionElement, } from '../../../common/service_map'; import { ConnectionsResponse, ServicesResponse } from './get_service_map'; import { ServiceAnomaliesResponse } from './get_service_anomalies'; +import { groupResourceNodes } from './group_resource_nodes'; function getConnectionNodeId(node: ConnectionNode): string { if ('span.destination.service.resource' in node) { @@ -213,9 +215,12 @@ export function transformServiceMapResponses(response: ServiceMapResponse) { }, []); // Put everything together in elements, with everything in the "data" property - const elements = [...dedupedConnections, ...dedupedNodes].map((element) => ({ + const elements: ConnectionElement[] = [ + ...dedupedConnections, + ...dedupedNodes, + ].map((element) => ({ data: element, })); - return { elements }; + return groupResourceNodes({ elements }); } diff --git a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts index 27cd9b53f8349..3c3eaaca7efdb 100644 --- a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts +++ b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @@ -19,8 +19,10 @@ import { TRANSACTION_PAGE_LOAD } from '../../common/transaction_types'; export function getRumPageLoadTransactionsProjection({ setup, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; }) { const { start, end, uiFiltersES } = setup; @@ -35,6 +37,17 @@ export function getRumPageLoadTransactionsProjection({ field: 'transaction.marks.navigationTiming.fetchStart', }, }, + ...(urlQuery + ? [ + { + wildcard: { + 'url.full': { + value: `*${urlQuery}*`, + }, + }, + }, + ] + : []), ...uiFiltersES, ], }; diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 7d9a9ccc167e0..f975ab177f147 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -77,6 +77,7 @@ import { rumServicesRoute, rumVisitorsBreakdownRoute, rumWebCoreVitals, + rumUrlSearch, rumLongTaskMetrics, } from './rum_client'; import { @@ -173,6 +174,7 @@ const createApmApi = () => { .add(rumServicesRoute) .add(rumVisitorsBreakdownRoute) .add(rumWebCoreVitals) + .add(rumUrlSearch) .add(rumLongTaskMetrics) // Observability dashboard diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts index 179279b6f2d8a..e3a846f9fb5c7 100644 --- a/x-pack/plugins/apm/server/routes/rum_client.ts +++ b/x-pack/plugins/apm/server/routes/rum_client.ts @@ -16,37 +16,54 @@ import { getRumServices } from '../lib/rum_client/get_rum_services'; import { getVisitorBreakdown } from '../lib/rum_client/get_visitor_breakdown'; import { getWebCoreVitals } from '../lib/rum_client/get_web_core_vitals'; import { getLongTaskMetrics } from '../lib/rum_client/get_long_task_metrics'; +import { getUrlSearch } from '../lib/rum_client/get_url_search'; export const percentileRangeRt = t.partial({ minPercentile: t.string, maxPercentile: t.string, }); +const urlQueryRt = t.partial({ urlQuery: t.string }); + export const rumClientMetricsRoute = createRoute(() => ({ path: '/api/apm/rum/client-metrics', params: { - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]), }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return getClientMetrics({ setup }); + const { + query: { urlQuery }, + } = context.params; + + return getClientMetrics({ setup, urlQuery }); }, })); export const rumPageLoadDistributionRoute = createRoute(() => ({ path: '/api/apm/rum-client/page-load-distribution', params: { - query: t.intersection([uiFiltersRt, rangeRt, percentileRangeRt]), + query: t.intersection([ + uiFiltersRt, + rangeRt, + percentileRangeRt, + urlQueryRt, + ]), }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { - query: { minPercentile, maxPercentile }, + query: { minPercentile, maxPercentile, urlQuery }, } = context.params; - return getPageLoadDistribution({ setup, minPercentile, maxPercentile }); + return getPageLoadDistribution({ + setup, + minPercentile, + maxPercentile, + urlQuery, + }); }, })); @@ -57,6 +74,7 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({ uiFiltersRt, rangeRt, percentileRangeRt, + urlQueryRt, t.type({ breakdown: t.string }), ]), }, @@ -64,7 +82,7 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({ const setup = await setupRequest(context, request); const { - query: { minPercentile, maxPercentile, breakdown }, + query: { minPercentile, maxPercentile, breakdown, urlQuery }, } = context.params; return getPageLoadDistBreakdown({ @@ -72,6 +90,7 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({ minDuration: Number(minPercentile), maxDuration: Number(maxPercentile), breakdown, + urlQuery, }); }, })); @@ -82,6 +101,7 @@ export const rumPageViewsTrendRoute = createRoute(() => ({ query: t.intersection([ uiFiltersRt, rangeRt, + urlQueryRt, t.partial({ breakdowns: t.string }), ]), }, @@ -89,10 +109,10 @@ export const rumPageViewsTrendRoute = createRoute(() => ({ const setup = await setupRequest(context, request); const { - query: { breakdowns }, + query: { breakdowns, urlQuery }, } = context.params; - return getPageViewTrends({ setup, breakdowns }); + return getPageViewTrends({ setup, breakdowns, urlQuery }); }, })); @@ -111,35 +131,63 @@ export const rumServicesRoute = createRoute(() => ({ export const rumVisitorsBreakdownRoute = createRoute(() => ({ path: '/api/apm/rum-client/visitor-breakdown', params: { - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]), }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return getVisitorBreakdown({ setup }); + const { + query: { urlQuery }, + } = context.params; + + return getVisitorBreakdown({ setup, urlQuery }); }, })); export const rumWebCoreVitals = createRoute(() => ({ path: '/api/apm/rum-client/web-core-vitals', params: { - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]), }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return getWebCoreVitals({ setup }); + const { + query: { urlQuery }, + } = context.params; + + return getWebCoreVitals({ setup, urlQuery }); }, })); export const rumLongTaskMetrics = createRoute(() => ({ path: '/api/apm/rum-client/long-task-metrics', params: { - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]), }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return getLongTaskMetrics({ setup }); + const { + query: { urlQuery }, + } = context.params; + + return getLongTaskMetrics({ setup, urlQuery }); + }, +})); + +export const rumUrlSearch = createRoute(() => ({ + path: '/api/apm/rum-client/url-search', + params: { + query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]), + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + + const { + query: { urlQuery }, + } = context.params; + + return getUrlSearch({ setup, urlQuery }); }, })); diff --git a/x-pack/plugins/apm/typings/apm_rum_react.d.ts b/x-pack/plugins/apm/typings/apm_rum_react.d.ts index 1c3e41ec12780..f9eafef59f55d 100644 --- a/x-pack/plugins/apm/typings/apm_rum_react.d.ts +++ b/x-pack/plugins/apm/typings/apm_rum_react.d.ts @@ -3,6 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + declare module '@elastic/apm-rum-react' { - export const ApmRoute: any; + import { RouteProps } from 'react-router-dom'; + + export const ApmRoute: React.ComponentClass; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts index 33260b5c9303f..df205dc742b07 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts @@ -49,4 +49,9 @@ describe('savedLens', () => { expect(expression.input.filters).toEqual(embeddableFilters); }); + + it('accepts an empty title when title is disabled', () => { + const expression = fn(null, { ...args, title: '' }, {} as any); + expect(expression.input.title).toEqual(''); + }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts index 49b8c5562af65..a823d0606d46f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts @@ -72,7 +72,7 @@ export function savedLens(): ExpressionFunctionDefinition< id: args.id, filters: getQueryFilters(filters), timeRange: args.timerange || defaultTimeRange, - title: args.title ? args.title : undefined, + title: args.title === null ? undefined : args.title, disableTriggers: true, }, embeddableType: EmbeddableTypes.lens, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts index ec640cfb5b299..a64ff7da2aa19 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts @@ -93,7 +93,7 @@ export function savedMap(): ExpressionFunctionDefinition< mapCenter: center, hideFilterActions: true, - title: args.title ? args.title : undefined, + title: args.title === null ? undefined : args.title, isLayerTOCOpen: false, hiddenLayers: args.hideLayer || [], }, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts index a64fb167dd19f..7902d09a0bdf1 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts @@ -36,6 +36,7 @@ describe('savedVisualization', () => { timerange: null, colors: null, hideLegend: null, + title: null, }; it('accepts null context', () => { @@ -50,4 +51,9 @@ describe('savedVisualization', () => { expect(expression.input.filters).toEqual(embeddableFilters); }); + + it('accepts an empty title when title is disabled', () => { + const expression = fn(null, { ...args, title: '' }, {} as any); + expect(expression.input.title).toEqual(''); + }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts index 2782ca039d7ed..449be2db43d44 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts @@ -20,6 +20,7 @@ interface Arguments { timerange: TimeRangeArg | null; colors: SeriesStyle[] | null; hideLegend: boolean | null; + title: string | null; } type Output = EmbeddableExpression; @@ -61,9 +62,14 @@ export function savedVisualization(): ExpressionFunctionDefinition< help: argHelp.hideLegend, required: false, }, + title: { + types: ['string'], + help: argHelp.title, + required: false, + }, }, type: EmbeddableExpressionType, - fn: (input, { id, timerange, colors, hideLegend }) => { + fn: (input, { id, timerange, colors, hideLegend, title }) => { const filters = input ? input.and : []; const visOptions: VisualizeInput['vis'] = {}; @@ -90,6 +96,7 @@ export function savedVisualization(): ExpressionFunctionDefinition< timeRange: timerange || defaultTimeRange, filters: getQueryFilters(filters), vis: visOptions, + title: title === null ? undefined : title, }, embeddableType: EmbeddableTypes.visualization, generatedAt: Date.now(), diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.test.ts index 7bcfd6bef4620..0df39f281da9c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.test.ts @@ -52,4 +52,16 @@ describe('toExpression', () => { expect(timerangeExpression.chain[0].arguments.from[0]).toEqual(input.timeRange?.from); expect(timerangeExpression.chain[0].arguments.to[0]).toEqual(input.timeRange?.to); }); + + it('includes empty panel title', () => { + const input: SavedLensInput = { + ...baseEmbeddableInput, + title: '', + }; + + const expression = toExpression(input); + const ast = fromExpression(expression); + + expect(ast.chain[0].arguments).toHaveProperty('title', [input.title]); + }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.ts index 5bb45c5ca129e..a8e200dd3e4ba 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.ts @@ -13,7 +13,7 @@ export function toExpression(input: SavedLensInput): string { expressionParts.push(`id="${input.id}"`); - if (input.title) { + if (input.title !== undefined) { expressionParts.push(`title="${input.title}"`); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts index d910c734a6974..d2c803a1ff208 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts @@ -69,4 +69,16 @@ describe('toExpression', () => { expect(timerangeExpression.chain[0].arguments.from[0]).toEqual(input.timeRange?.from); expect(timerangeExpression.chain[0].arguments.to[0]).toEqual(input.timeRange?.to); }); + + it('includes empty panel title', () => { + const input: MapEmbeddableInput = { + ...baseSavedMapInput, + title: '', + }; + + const expression = toExpression(input); + const ast = fromExpression(expression); + + expect(ast.chain[0].arguments).toHaveProperty('title', [input.title]); + }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts index 111fdc71fa242..769c2c9e10e9c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts @@ -12,7 +12,7 @@ export function toExpression(input: MapEmbeddableInput): string { expressionParts.push('savedMap'); expressionParts.push(`id="${input.id}"`); - if (input.title) { + if (input.title !== undefined) { expressionParts.push(`title="${input.title}"`); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts index 07f828755e46f..4550a90ce98a2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts @@ -69,4 +69,16 @@ describe('toExpression', () => { expect(aColor?.chain[0].arguments.color[0]).toBe(colorMap.a); expect(bColor?.chain[0].arguments.color[0]).toBe(colorMap.b); }); + + it('includes empty panel title', () => { + const input = { + ...baseInput, + title: '', + }; + + const expression = toExpression(input); + const ast = fromExpression(expression); + + expect(ast.chain[0].arguments).toHaveProperty('title', [input.title]); + }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts index f03c10e2d424e..a8adbf9d2d860 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts @@ -12,6 +12,10 @@ export function toExpression(input: VisualizeInput): string { expressionParts.push('savedVisualization'); expressionParts.push(`id="${input.id}"`); + if (input.title !== undefined) { + expressionParts.push(`title="${input.title}"`); + } + if (input.timeRange) { expressionParts.push( `timerange={timerange from="${input.timeRange.from}" to="${input.timeRange.to}"}` diff --git a/x-pack/plugins/canvas/i18n/functions/dict/saved_visualization.ts b/x-pack/plugins/canvas/i18n/functions/dict/saved_visualization.ts index e8cbddc5c1102..257e251fe2bc2 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/saved_visualization.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/saved_visualization.ts @@ -29,5 +29,8 @@ export const help: FunctionHelp> = { defaultMessage: `Specifies the option to hide the legend`, } ), + title: i18n.translate('xpack.canvas.functions.savedVisualization.args.titleHelpText', { + defaultMessage: `The title for the visualization object`, + }), }, }; diff --git a/x-pack/plugins/canvas/public/components/datatable/datatable.scss b/x-pack/plugins/canvas/public/components/datatable/datatable.scss index bd11bff18e091..8e36de3b84423 100644 --- a/x-pack/plugins/canvas/public/components/datatable/datatable.scss +++ b/x-pack/plugins/canvas/public/components/datatable/datatable.scss @@ -4,7 +4,6 @@ display: flex; flex-direction: column; justify-content: space-between; - font-size: $euiFontSizeS; .canvasDataTable__tableWrapper { @include euiScrollBar; @@ -33,7 +32,6 @@ .canvasDataTable__th, .canvasDataTable__td { - text-align: left; padding: $euiSizeS $euiSizeXS; border-bottom: $euiBorderThin; } diff --git a/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot b/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot index 99d5dc3c115be..5c17eb2b68137 100644 --- a/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot @@ -16,18 +16,7 @@ exports[`Storyshots components/ExpressionInput default 1`] = ` id="generated-id" onBlur={[Function]} onFocus={[Function]} - > -
-
-
+ />
diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index f7ae9fc6d0f91..c8fe72e6f2c1e 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -13,6 +13,7 @@ import { SearchInterceptorDeps, UI_SETTINGS, } from '../../../../../src/plugins/data/public'; +import { isErrorResponse, isCompleteResponse } from '../../../../../src/plugins/data/public'; import { AbortError, toPromise } from '../../../../../src/plugins/data/common'; import { IAsyncSearchOptions } from '.'; import { IAsyncSearchRequest, ENHANCED_ES_SEARCH_STRATEGY } from '../../common'; @@ -66,12 +67,12 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { return this.runSearch(request, combinedSignal, strategy).pipe( expand((response) => { // If the response indicates of an error, stop polling and complete the observable - if (!response || (!response.isRunning && response.isPartial)) { + if (isErrorResponse(response)) { return throwError(new AbortError()); } // If the response indicates it is complete, stop polling and complete the observable - if (!response.isRunning) { + if (isCompleteResponse(response)) { return EMPTY; } diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 72ea1f096e8fb..f3cf67a487a68 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -19,7 +19,11 @@ import { shimHitsTotal, } from '../../../../../src/plugins/data/server'; import { IEnhancedEsSearchRequest } from '../../common'; -import { ISearchOptions, IEsSearchResponse } from '../../../../../src/plugins/data/common/search'; +import { + ISearchOptions, + IEsSearchResponse, + isCompleteResponse, +} from '../../../../../src/plugins/data/common/search'; function isEnhancedEsSearchResponse(response: any): response is IEsSearchResponse { return response.hasOwnProperty('isPartial') && response.hasOwnProperty('isRunning'); @@ -48,8 +52,7 @@ export const enhancedEsSearchStrategyProvider = ( usage && isAsync && isEnhancedEsSearchResponse(response) && - !response.isRunning && - !response.isPartial + isCompleteResponse(response) ) { usage.trackSuccess(response.rawResponse.took); } diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md b/x-pack/plugins/drilldowns/url_drilldown/README.md similarity index 65% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md rename to x-pack/plugins/drilldowns/url_drilldown/README.md index 996723ccb914d..8eedc44ca35ae 100644 --- a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md +++ b/x-pack/plugins/drilldowns/url_drilldown/README.md @@ -1,24 +1,26 @@ -# Basic url drilldown implementation +## URL drilldown + +> NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to `ui_actions_enhanced` plugin. Url drilldown allows navigating to external URL or to internal kibana URL. By using variables in url template result url can be dynamic and depend on user's interaction. URL drilldown has 3 sources for variables: -- Global static variables like, for example, `kibanaUrl`. Such variables won’t change depending on a place where url drilldown is used. -- Context variables are dynamic and different depending on where drilldown is created and used. -- Event variables depend on a trigger context. These variables are dynamically extracted from the action context when drilldown is executed. +1. Global static variables like, for example, `kibanaUrl`. Such variables won’t change depending on a place where url drilldown is used. +2. Context variables are dynamic and different depending on where drilldown is created and used. +3. Event variables depend on a trigger context. These variables are dynamically extracted from the action context when drilldown is executed. Difference between `event` and `context` variables, is that real `context` variables are available during drilldown creation (e.g. embeddable panel), but `event` variables mapped from trigger context. Since there is no trigger context during drilldown creation, we have to provide some _mock_ variables for validating and previewing the URL. In current implementation url drilldown has to be used inside the embeddable and with `ValueClickTrigger` or `RangeSelectTrigger`. -- `context` variables extracted from `embeddable` -- `event` variables extracted from `trigger` context +* `context` variables extracted from `embeddable` +* `event` variables extracted from `trigger` context In future this basic url drilldown implementation would allow injecting more variables into `context` (e.g. `dashboard` app specific variables) and would allow providing support for new trigger types from outside. This extensibility improvements are tracked here: https://github.com/elastic/kibana/issues/55324 In case a solution app has a use case for url drilldown that has to be different from current basic implementation and -just extending variables list is not enough, then recommendation is to create own custom url drilldown and reuse building blocks from `ui_actions_enhanced`. +just extending variables list is not enough, then recommendation is to create own custom url drilldown and reuse building blocks from `ui_actions_enhanced`. \ No newline at end of file diff --git a/x-pack/plugins/drilldowns/url_drilldown/kibana.json b/x-pack/plugins/drilldowns/url_drilldown/kibana.json new file mode 100644 index 0000000000000..9bdd13fbfea26 --- /dev/null +++ b/x-pack/plugins/drilldowns/url_drilldown/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "urlDrilldown", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["embeddable", "uiActions", "uiActionsEnhanced"], + "requiredBundles": ["kibanaUtils", "kibanaReact"] +} diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/index.ts b/x-pack/plugins/drilldowns/url_drilldown/public/index.ts new file mode 100644 index 0000000000000..b040ef625bc1f --- /dev/null +++ b/x-pack/plugins/drilldowns/url_drilldown/public/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { UrlDrilldownPlugin } from './plugin'; + +export function plugin(context: PluginInitializerContext) { + return new UrlDrilldownPlugin(context); +} diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts similarity index 62% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts index 748f6f4cecedd..7e91c6b849035 100644 --- a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts @@ -6,9 +6,6 @@ import { i18n } from '@kbn/i18n'; -export const txtUrlDrilldownDisplayName = i18n.translate( - 'xpack.embeddableEnhanced.drilldowns.urlDrilldownDisplayName', - { - defaultMessage: 'Go to URL', - } -); +export const txtUrlDrilldownDisplayName = i18n.translate('xpack.urlDrilldown.DisplayName', { + defaultMessage: 'Go to URL', +}); diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/index.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/index.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/index.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/index.ts diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.test.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.tsx rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.test.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts b/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts new file mode 100644 index 0000000000000..82ce7a129f497 --- /dev/null +++ b/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; +import { EmbeddableSetup, EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; +import { + AdvancedUiActionsSetup, + AdvancedUiActionsStart, + urlDrilldownGlobalScopeProvider, +} from '../../../ui_actions_enhanced/public'; +import { UrlDrilldown } from './lib'; +import { createStartServicesGetter } from '../../../../../src/plugins/kibana_utils/public'; + +export interface SetupDependencies { + embeddable: EmbeddableSetup; + uiActionsEnhanced: AdvancedUiActionsSetup; +} + +export interface StartDependencies { + embeddable: EmbeddableStart; + uiActionsEnhanced: AdvancedUiActionsStart; +} + +// eslint-disable-next-line +export interface SetupContract {} + +// eslint-disable-next-line +export interface StartContract {} + +export class UrlDrilldownPlugin + implements Plugin { + constructor(protected readonly context: PluginInitializerContext) {} + + public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { + const startServices = createStartServicesGetter(core.getStartServices); + plugins.uiActionsEnhanced.registerDrilldown( + new UrlDrilldown({ + getGlobalScope: urlDrilldownGlobalScopeProvider({ core }), + navigateToUrl: (url: string) => + core.getStartServices().then(([{ application }]) => application.navigateToUrl(url)), + getSyntaxHelpDocsLink: () => + startServices().core.docLinks.links.dashboard.urlDrilldownTemplateSyntax, + getVariablesHelpDocsLink: () => + startServices().core.docLinks.links.dashboard.urlDrilldownVariables, + }) + ); + + return {}; + } + + public start(core: CoreStart, plugins: StartDependencies): StartContract { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/embeddable_enhanced/kibana.json b/x-pack/plugins/embeddable_enhanced/kibana.json index acada946fe0d1..8d49e3e26eb7b 100644 --- a/x-pack/plugins/embeddable_enhanced/kibana.json +++ b/x-pack/plugins/embeddable_enhanced/kibana.json @@ -3,6 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"], - "requiredBundles": ["kibanaUtils"] + "requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"] } diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index 2138a372523b7..5d5ad852839d4 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -28,11 +28,8 @@ import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, AdvancedUiActionsSetup, AdvancedUiActionsStart, - urlDrilldownGlobalScopeProvider, } from '../../ui_actions_enhanced/public'; import { PanelNotificationsAction, ACTION_PANEL_NOTIFICATIONS } from './actions'; -import { UrlDrilldown } from './drilldowns'; -import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; declare module '../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -64,23 +61,10 @@ export class EmbeddableEnhancedPlugin public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.setCustomEmbeddableFactoryProvider(plugins); - const startServices = createStartServicesGetter(core.getStartServices); const panelNotificationAction = new PanelNotificationsAction(); plugins.uiActionsEnhanced.registerAction(panelNotificationAction); plugins.uiActionsEnhanced.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id); - plugins.uiActionsEnhanced.registerDrilldown( - new UrlDrilldown({ - getGlobalScope: urlDrilldownGlobalScopeProvider({ core }), - navigateToUrl: (url: string) => - core.getStartServices().then(([{ application }]) => application.navigateToUrl(url)), - getSyntaxHelpDocsLink: () => - startServices().core.docLinks.links.dashboard.urlDrilldownTemplateSyntax, - getVariablesHelpDocsLink: () => - startServices().core.docLinks.links.dashboard.urlDrilldownVariables, - }) - ); - return {}; } diff --git a/x-pack/legacy/server/lib/constants/xpack_info.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/enterprise_search_url.mock.ts similarity index 66% rename from x-pack/legacy/server/lib/constants/xpack_info.ts rename to x-pack/plugins/enterprise_search/public/applications/__mocks__/enterprise_search_url.mock.ts index c58bb275245b6..47660d0a31720 100644 --- a/x-pack/legacy/server/lib/constants/xpack_info.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/enterprise_search_url.mock.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS = 30001; // 30 seconds +import { externalUrl } from '../shared/enterprise_search_url'; + +externalUrl.enterpriseSearchUrl = 'http://localhost:3002'; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/flash_messages_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/flash_messages_logic.mock.ts new file mode 100644 index 0000000000000..a610ea0238ac0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/flash_messages_logic.mock.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const mockFlashMessagesValues = { + messages: [], + queuedMessages: [], +}; + +export const mockFlashMessagesActions = { + setFlashMessages: jest.fn(), + clearFlashMessages: jest.fn(), + setQueuedMessages: jest.fn(), + clearQueuedMessages: jest.fn(), +}; diff --git a/x-pack/legacy/common/poller.d.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/http_logic.mock.ts similarity index 56% rename from x-pack/legacy/common/poller.d.ts rename to x-pack/plugins/enterprise_search/public/applications/__mocks__/http_logic.mock.ts index df39d93a28a81..e77863c70c23a 100644 --- a/x-pack/legacy/common/poller.d.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/http_logic.mock.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export declare class Poller { - constructor(options: any); +import { httpServiceMock } from 'src/core/public/mocks'; - public start(): void; - public stop(): void; - public isRunning(): boolean; - public getPollFrequency(): number; -} +export const mockHttpValues = { + http: httpServiceMock.createSetupContract(), + errorConnecting: false, + readOnlyMode: false, +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts index e999d40a3f8e6..88a900f69c5ec 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts @@ -6,7 +6,11 @@ export { mockHistory, mockLocation } from './react_router_history.mock'; export { mockKibanaContext } from './kibana_context.mock'; -export { mockLicenseContext } from './license_context.mock'; +export { mockLicensingValues } from './licensing_logic.mock'; +export { mockHttpValues } from './http_logic.mock'; +export { mockFlashMessagesValues, mockFlashMessagesActions } from './flash_messages_logic.mock'; +export { mockAllValues, mockAllActions, setMockValues } from './kea.mock'; + export { mountWithContext, mountWithKibanaContext, diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts index 5049e9da21ce9..bad6beaa1652e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts @@ -4,21 +4,50 @@ * you may not use this file except in compliance with the Elastic License. */ +/** + * Combine all shared mock values/actions into a single obj + * + * NOTE: These variable names MUST start with 'mock*' in order for + * Jest to accept its use within a jest.mock() + */ +import { mockLicensingValues } from './licensing_logic.mock'; +import { mockHttpValues } from './http_logic.mock'; +import { mockFlashMessagesValues, mockFlashMessagesActions } from './flash_messages_logic.mock'; + +export const mockAllValues = { + ...mockLicensingValues, + ...mockHttpValues, + ...mockFlashMessagesValues, +}; +export const mockAllActions = { + ...mockFlashMessagesActions, +}; + +/** + * Import this file directly to mock useValues with a set of default values for all shared logic files. + * Example usage: + * + * import '../../../__mocks__/kea'; // Must come before kea's import, adjust relative path as needed + */ jest.mock('kea', () => ({ ...(jest.requireActual('kea') as object), - useValues: jest.fn(() => ({})), - useActions: jest.fn(() => ({})), + useValues: jest.fn(() => ({ ...mockAllValues })), + useActions: jest.fn(() => ({ ...mockAllActions })), })); /** + * Call this function to override a specific set of Kea values while retaining all other defaults * Example usage within a component test: * - * import '../../../__mocks__/kea'; // Must come before kea's import, adjust relative path as needed - * - * import { useActions, useValues } from 'kea'; + * import '../../../__mocks__/kea'; + * import { setMockValues } from ''../../../__mocks__'; * * it('some test', () => { - * (useValues as jest.Mock).mockImplementationOnce(() => ({ someValue: 'hello' })); - * (useActions as jest.Mock).mockImplementationOnce(() => ({ someAction: () => 'world' })); + * setMockValues({ someValue: 'hello' }); * }); */ +import { useValues } from 'kea'; + +export const setMockValues = (values: object) => { + (useValues as jest.Mock).mockImplementation(() => ({ ...mockAllValues, ...values })); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts index 890072ab42eb9..ee77b0937cd82 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts @@ -4,18 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { httpServiceMock } from 'src/core/public/mocks'; -import { ExternalUrl } from '../shared/enterprise_search_url'; - /** * A set of default Kibana context values to use across component tests. * @see enterprise_search/public/index.tsx for the KibanaContext definition/import */ export const mockKibanaContext = { - http: httpServiceMock.createSetupContract(), navigateToUrl: jest.fn(), setBreadcrumbs: jest.fn(), setDocTitle: jest.fn(), config: { host: 'http://localhost:3002' }, - externalUrl: new ExternalUrl('http://localhost:3002'), }; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/licensing_logic.mock.ts similarity index 79% rename from x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts rename to x-pack/plugins/enterprise_search/public/applications/__mocks__/licensing_logic.mock.ts index 7c37ecc7cde1b..51b32e7a877b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/licensing_logic.mock.ts @@ -6,6 +6,8 @@ import { licensingMock } from '../../../../licensing/public/mocks'; -export const mockLicenseContext = { +export const mockLicensingValues = { license: licensingMock.createLicense(), + hasPlatinumLicense: false, + hasGoldLicense: false, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx index 826e0482acef7..646c3104c286f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx @@ -15,8 +15,6 @@ import { getContext, resetContext } from 'kea'; import { I18nProvider } from '@kbn/i18n/react'; import { KibanaContext } from '../'; import { mockKibanaContext } from './kibana_context.mock'; -import { LicenseContext } from '../shared/licensing'; -import { mockLicenseContext } from './license_context.mock'; /** * This helper mounts a component with all the contexts/providers used @@ -34,9 +32,7 @@ export const mountWithContext = (children: React.ReactNode, context?: object) => return mount( - - {children} - + {children} ); @@ -67,7 +63,7 @@ export const mountWithKibanaContext = (children: React.ReactNode, context?: obje */ export const mountWithAsyncContext = async ( children: React.ReactNode, - context: object + context?: object ): Promise => { let wrapper: ReactWrapper | undefined; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts index 842dcefd3aef8..7b3ac86ad0ab1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts @@ -14,6 +14,7 @@ export const mockHistory = { location: { pathname: '/current-path', }, + listen: jest.fn(() => jest.fn()), }; export const mockLocation = { key: 'someKey', diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts index 3a2193db646de..df9e58994e36b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts @@ -9,11 +9,10 @@ * Jest to accept its use within a jest.mock() */ import { mockKibanaContext } from './kibana_context.mock'; -import { mockLicenseContext } from './license_context.mock'; jest.mock('react', () => ({ ...(jest.requireActual('react') as object), - useContext: jest.fn(() => ({ ...mockKibanaContext, ...mockLicenseContext })), + useContext: jest.fn(() => ({ ...mockKibanaContext })), useEffect: jest.fn((fn) => fn()), // Calls on mount/every update - use mount for more complex behavior })); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.test.tsx index 7e6876bc9b3a4..53f50822cf653 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../../__mocks__/shallow_usecontext.mock'; +import '../../../../__mocks__/kea.mock'; import React from 'react'; import { shallow } from 'enzyme'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx index 58691cf09b4a5..cfe88d00ce14e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; +import { useValues } from 'kea'; import { EuiPageContent, EuiEmptyPrompt, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { sendTelemetry } from '../../../../shared/telemetry'; +import { HttpLogic } from '../../../../shared/http'; +import { getAppSearchUrl } from '../../../../shared/enterprise_search_url'; import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; -import { KibanaContext, IKibanaContext } from '../../../../index'; import { CREATE_ENGINES_PATH } from '../../../routes'; import { EngineOverviewHeader } from './header'; @@ -18,10 +20,7 @@ import { EngineOverviewHeader } from './header'; import './empty_state.scss'; export const EmptyState: React.FC = () => { - const { - externalUrl: { getAppSearchUrl }, - http, - } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); const buttonProps = { href: getAppSearchUrl(CREATE_ENGINES_PATH), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.test.tsx index 7f22ce132d405..78ee5764be5a9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.test.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../../__mocks__/shallow_usecontext.mock'; +import '../../../../__mocks__/kea.mock'; +import '../../../../__mocks__/enterprise_search_url.mock'; import React from 'react'; import { shallow } from 'enzyme'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx index 1a1ae295d4828..6ebb2c5bf453d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; +import { useValues } from 'kea'; import { EuiPageHeader, EuiPageHeaderSection, @@ -16,13 +17,11 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { sendTelemetry } from '../../../../shared/telemetry'; -import { KibanaContext, IKibanaContext } from '../../../../index'; +import { HttpLogic } from '../../../../shared/http'; +import { getAppSearchUrl } from '../../../../shared/enterprise_search_url'; export const EngineOverviewHeader: React.FC = () => { - const { - externalUrl: { getAppSearchUrl }, - http, - } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); const buttonProps = { fill: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx index c2379fb33bd71..44afce96c1a6c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../../__mocks__/kea.mock'; import '../../../__mocks__/react_router_history.mock'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { shallow, ReactWrapper } from 'enzyme'; -import { mountWithAsyncContext, mockKibanaContext } from '../../../__mocks__'; +import { mountWithAsyncContext, mockHttpValues, setMockValues } from '../../../__mocks__'; import { LoadingState, EmptyState } from './components'; import { EngineTable } from './engine_table'; @@ -18,8 +19,6 @@ import { EngineTable } from './engine_table'; import { EngineOverview } from './'; describe('EngineOverview', () => { - const mockHttp = mockKibanaContext.http; - describe('non-happy-path states', () => { it('isLoading', () => { const wrapper = shallow(); @@ -28,15 +27,16 @@ describe('EngineOverview', () => { }); it('isEmpty', async () => { - const wrapper = await mountWithAsyncContext(, { + setMockValues({ http: { - ...mockHttp, + ...mockHttpValues.http, get: () => ({ results: [], meta: { page: { total_results: 0 } }, }), }, }); + const wrapper = await mountWithAsyncContext(); expect(wrapper.find(EmptyState)).toHaveLength(1); }); @@ -65,12 +65,11 @@ describe('EngineOverview', () => { beforeEach(() => { jest.clearAllMocks(); + setMockValues({ http: { ...mockHttpValues.http, get: mockApi } }); }); it('renders and calls the engines API', async () => { - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, - }); + const wrapper = await mountWithAsyncContext(); expect(wrapper.find(EngineTable)).toHaveLength(1); expect(mockApi).toHaveBeenNthCalledWith(1, '/api/app_search/engines', { @@ -83,10 +82,11 @@ describe('EngineOverview', () => { describe('when on a platinum license', () => { it('renders a 2nd meta engines table & makes a 2nd meta engines API call', async () => { - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, - license: { type: 'platinum', isActive: true }, + setMockValues({ + hasPlatinumLicense: true, + http: { ...mockHttpValues.http, get: mockApi }, }); + const wrapper = await mountWithAsyncContext(); expect(wrapper.find(EngineTable)).toHaveLength(2); expect(mockApi).toHaveBeenNthCalledWith(2, '/api/app_search/engines', { @@ -103,9 +103,7 @@ describe('EngineOverview', () => { wrapper.find(EngineTable).prop('pagination'); it('passes down page data from the API', async () => { - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, - }); + const wrapper = await mountWithAsyncContext(); const pagination = getTablePagination(wrapper); expect(pagination.totalEngines).toEqual(100); @@ -113,9 +111,7 @@ describe('EngineOverview', () => { }); it('re-polls the API on page change', async () => { - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, - }); + const wrapper = await mountWithAsyncContext(); await act(async () => getTablePagination(wrapper).onPaginate(5)); wrapper.update(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx index 9703fde7e140a..0cb9ba106dbb8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { useValues } from 'kea'; import { EuiPageContent, EuiPageContentHeader, @@ -12,13 +13,13 @@ import { EuiTitle, EuiSpacer, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; import { FlashMessages } from '../../../shared/flash_messages'; -import { LicenseContext, ILicenseContext, hasPlatinumLicense } from '../../../shared/licensing'; -import { KibanaContext, IKibanaContext } from '../../../index'; +import { HttpLogic } from '../../../shared/http'; +import { LicensingLogic } from '../../../shared/licensing'; import { EngineIcon } from './assets/engine_icon'; import { MetaEngineIcon } from './assets/meta_engine_icon'; @@ -38,8 +39,8 @@ interface ISetEnginesCallbacks { } export const EngineOverview: React.FC = () => { - const { http } = useContext(KibanaContext) as IKibanaContext; - const { license } = useContext(LicenseContext) as ILicenseContext; + const { http } = useValues(HttpLogic); + const { hasPlatinumLicense } = useValues(LicensingLogic); const [isLoading, setIsLoading] = useState(true); const [engines, setEngines] = useState([]); @@ -71,13 +72,13 @@ export const EngineOverview: React.FC = () => { }, [enginesPage]); useEffect(() => { - if (hasPlatinumLicense(license)) { + if (hasPlatinumLicense) { const params = { type: 'meta', pageIndex: metaEnginesPage }; const callbacks = { setResults: setMetaEngines, setResultsTotal: setMetaEnginesTotal }; setEnginesData(params, callbacks); } - }, [license, metaEnginesPage]); + }, [hasPlatinumLicense, metaEnginesPage]); if (isLoading) return ; if (!engines.length) return ; @@ -94,10 +95,9 @@ export const EngineOverview: React.FC = () => {

- + {i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.engines', { + defaultMessage: 'Engines', + })}

@@ -119,10 +119,9 @@ export const EngineOverview: React.FC = () => {

- + {i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.metaEngines', { + defaultMessage: 'Meta Engines', + })}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx index 46b6e61e352de..c66fd24fee12a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx @@ -4,10 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../../__mocks__/kea.mock'; +import '../../../__mocks__/enterprise_search_url.mock'; +import { mockHttpValues } from '../../../__mocks__/'; + import React from 'react'; +import { mount } from 'enzyme'; +import { I18nProvider } from '@kbn/i18n/react'; import { EuiBasicTable, EuiPagination, EuiButtonEmpty, EuiLink } from '@elastic/eui'; -import { mountWithContext } from '../../../__mocks__'; jest.mock('../../../shared/telemetry', () => ({ sendTelemetry: jest.fn() })); import { sendTelemetry } from '../../../shared/telemetry'; @@ -16,22 +21,24 @@ import { EngineTable } from './engine_table'; describe('EngineTable', () => { const onPaginate = jest.fn(); // onPaginate updates the engines API call upstream - const wrapper = mountWithContext( - + const wrapper = mount( + + + ); const table = wrapper.find(EuiBasicTable); @@ -56,7 +63,7 @@ describe('EngineTable', () => { link.simulate('click'); expect(sendTelemetry).toHaveBeenCalledWith({ - http: expect.any(Object), + http: mockHttpValues.http, product: 'app_search', action: 'clicked', metric: 'engine_table_link', @@ -71,10 +78,16 @@ describe('EngineTable', () => { }); it('handles empty data', () => { - const emptyWrapper = mountWithContext( - {} }} /> + const emptyWrapper = mount( + + {} }} + /> + ); const emptyTable = emptyWrapper.find(EuiBasicTable); + expect(emptyTable.prop('pagination').pageIndex).toEqual(0); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx index 9c6122c88c7d7..40fb313f30b31 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; +import { useValues } from 'kea'; import { EuiBasicTable, EuiBasicTableColumn, EuiLink } from '@elastic/eui'; import { FormattedMessage, FormattedDate, FormattedNumber } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { sendTelemetry } from '../../../shared/telemetry'; -import { KibanaContext, IKibanaContext } from '../../../index'; +import { HttpLogic } from '../../../shared/http'; +import { getAppSearchUrl } from '../../../shared/enterprise_search_url'; import { getEngineRoute } from '../../routes'; import { ENGINES_PAGE_SIZE } from '../../../../../common/constants'; @@ -40,10 +42,7 @@ export const EngineTable: React.FC = ({ data, pagination: { totalEngines, pageIndex, onPaginate }, }) => { - const { - externalUrl: { getAppSearchUrl }, - http, - } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); const engineLinkProps = (name: string) => ({ href: getAppSearchUrl(getEngineRoute(name)), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 350bc97085d7b..052f4446e4409 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -6,6 +6,7 @@ import '../__mocks__/shallow_usecontext.mock'; import '../__mocks__/kea.mock'; +import '../__mocks__/enterprise_search_url.mock'; import React, { useContext } from 'react'; import { Redirect } from 'react-router-dom'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index c848415daf612..410f6eb524822 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -11,6 +11,7 @@ import { useActions, useValues } from 'kea'; import { i18n } from '@kbn/i18n'; import { KibanaContext, IKibanaContext } from '../index'; +import { getAppSearchUrl } from '../shared/enterprise_search_url'; import { HttpLogic } from '../shared/http'; import { AppLogic } from './app_logic'; import { IInitialAppData } from '../../../common/types'; @@ -86,10 +87,6 @@ export const AppSearchConfigured: React.FC = (props) => { }; export const AppSearchNav: React.FC = () => { - const { - externalUrl: { getAppSearchUrl }, - } = useContext(KibanaContext) as IKibanaContext; - const { myRole: { canViewSettings, canViewAccountCredentials, canViewRoleMappings }, } = useValues(AppLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx index a76b654ccddd0..35301af44b413 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import '../../../__mocks__/kea.mock'; +import '../../../__mocks__/shallow_usecontext.mock'; + +import React, { useContext } from 'react'; import { shallow } from 'enzyme'; import { EuiCard } from '@elastic/eui'; @@ -24,6 +27,7 @@ describe('ProductCard', () => { }); it('renders an App Search card', () => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); const wrapper = shallow(); const card = wrapper.find(EuiCard).dive().shallow(); @@ -32,13 +36,14 @@ describe('ProductCard', () => { const button = card.find(EuiButton); expect(button.prop('to')).toEqual('/app/enterprise_search/app_search'); - expect(button.prop('data-test-subj')).toEqual('LaunchAppSearchButton'); + expect(button.prop('children')).toEqual('Launch App Search'); button.simulate('click'); expect(sendTelemetry).toHaveBeenCalledWith(expect.objectContaining({ metric: 'app_search' })); }); it('renders a Workplace Search card', () => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); const wrapper = shallow(); const card = wrapper.find(EuiCard).dive().shallow(); @@ -47,11 +52,21 @@ describe('ProductCard', () => { const button = card.find(EuiButton); expect(button.prop('to')).toEqual('/app/enterprise_search/workplace_search'); - expect(button.prop('data-test-subj')).toEqual('LaunchWorkplaceSearchButton'); + expect(button.prop('children')).toEqual('Launch Workplace Search'); button.simulate('click'); expect(sendTelemetry).toHaveBeenCalledWith( expect.objectContaining({ metric: 'workplace_search' }) ); }); + + it('renders correct button text when host not present', () => { + (useContext as jest.Mock).mockImplementation(() => ({ config: { host: '' } })); + + const wrapper = shallow(); + const card = wrapper.find(EuiCard).dive().shallow(); + const button = card.find(EuiButton); + + expect(button.prop('children')).toEqual('Setup Workplace Search'); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx index 334ca126cabb9..482d68736af01 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx @@ -5,14 +5,16 @@ */ import React, { useContext } from 'react'; -import upperFirst from 'lodash/upperFirst'; -import snakeCase from 'lodash/snakeCase'; +import { useValues } from 'kea'; +import { snakeCase } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiCard, EuiTextColor } from '@elastic/eui'; +import { KibanaContext, IKibanaContext } from '../../../index'; + import { EuiButton } from '../../../shared/react_router_helpers'; import { sendTelemetry } from '../../../shared/telemetry'; -import { KibanaContext, IKibanaContext } from '../../../index'; +import { HttpLogic } from '../../../shared/http'; import './product_card.scss'; @@ -28,7 +30,26 @@ interface IProductCard { } export const ProductCard: React.FC = ({ product, image }) => { - const { http } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); + const { + config: { host }, + } = useContext(KibanaContext) as IKibanaContext; + + const LAUNCH_BUTTON_TEXT = i18n.translate( + 'xpack.enterpriseSearch.overview.productCard.launchButton', + { + defaultMessage: 'Launch {productName}', + values: { productName: product.NAME }, + } + ); + + const SETUP_BUTTON_TEXT = i18n.translate( + 'xpack.enterpriseSearch.overview.productCard.setupButton', + { + defaultMessage: 'Setup {productName}', + values: { productName: product.NAME }, + } + ); return ( = ({ product, image }) => { metric: snakeCase(product.ID), }) } - data-test-subj={`Launch${upperFirst(product.ID)}Button`} > - {i18n.translate('xpack.enterpriseSearch.overview.productCard.button', { - defaultMessage: `Launch {productName}`, - values: { productName: product.NAME }, - })} + {host ? LAUNCH_BUTTON_TEXT : SETUP_BUTTON_TEXT} } /> diff --git a/x-pack/legacy/server/lib/create_router/is_es_error_factory/index.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/index.ts similarity index 80% rename from x-pack/legacy/server/lib/create_router/is_es_error_factory/index.js rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/index.ts index 441648a8701e0..b67d130cd68f0 100644 --- a/x-pack/legacy/server/lib/create_router/is_es_error_factory/index.js +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { isEsErrorFactory } from './is_es_error_factory'; +export { ProductSelector } from './product_selector'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.test.tsx new file mode 100644 index 0000000000000..44efa57db897f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../__mocks__/shallow_usecontext.mock'; + +import React, { useContext } from 'react'; +import { shallow } from 'enzyme'; +import { EuiPage } from '@elastic/eui'; + +import { ProductSelector } from './'; +import { ProductCard } from '../product_card'; + +describe('ProductSelector', () => { + it('renders the overview page and product cards with no host set', () => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: '' } })); + const wrapper = shallow(); + + expect(wrapper.find(EuiPage).hasClass('enterpriseSearchOverview')).toBe(true); + expect(wrapper.find(ProductCard)).toHaveLength(2); + }); + + describe('access checks when host is set', () => { + beforeEach(() => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); + }); + + it('does not render the App Search card if the user does not have access to AS', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(ProductCard)).toHaveLength(1); + expect(wrapper.find(ProductCard).prop('product').ID).toEqual('workplaceSearch'); + }); + + it('does not render the Workplace Search card if the user does not have access to WS', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(ProductCard)).toHaveLength(1); + expect(wrapper.find(ProductCard).prop('product').ID).toEqual('appSearch'); + }); + + it('does not render any cards if the user does not have access', () => { + const wrapper = shallow(); + + expect(wrapper.find(ProductCard)).toHaveLength(0); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx new file mode 100644 index 0000000000000..07b8d4b9926d7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; + +import { + EuiPage, + EuiPageBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiPageContentBody, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { KibanaContext, IKibanaContext } from '../../../index'; + +import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; + +import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; + +import { ProductCard } from '../product_card'; + +import AppSearchImage from '../../assets/app_search.png'; +import WorkplaceSearchImage from '../../assets/workplace_search.png'; + +interface IProductSelectorProps { + access: { + hasAppSearchAccess?: boolean; + hasWorkplaceSearchAccess?: boolean; + }; +} + +export const ProductSelector: React.FC = ({ access }) => { + const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access; + const { + config: { host }, + } = useContext(KibanaContext) as IKibanaContext; + + const shouldShowAppSearchCard = !host || hasAppSearchAccess; + const shouldShowWorkplaceSearchCard = !host || hasWorkplaceSearchAccess; + + return ( + + + + + + + + +

+ {i18n.translate('xpack.enterpriseSearch.overview.heading', { + defaultMessage: 'Welcome to Elastic Enterprise Search', + })} +

+
+ +

+ {i18n.translate('xpack.enterpriseSearch.overview.subheading', { + defaultMessage: 'Select a product to get started', + })} +

+
+
+
+ + + {shouldShowAppSearchCard && ( + + + + )} + {shouldShowWorkplaceSearchCard && ( + + + + )} + + + +
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/assets/getting_started.png b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/assets/getting_started.png new file mode 100644 index 0000000000000..f0fcb432c29e1 Binary files /dev/null and b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/assets/getting_started.png differ diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/index.ts similarity index 83% rename from x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/index.ts index 80baf7bf1a64d..c367424d375f9 100644 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { xpackInfoRoute } from './xpack_info'; +export { SetupGuide } from './setup_guide'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.test.tsx new file mode 100644 index 0000000000000..63b0cc5a56cd1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.test.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SetupGuide as SetupGuideLayout } from '../../../shared/setup_guide'; +import { SetupGuide } from './'; + +describe('SetupGuide', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(SetupGuideLayout)).toHaveLength(1); + expect(wrapper.find(SetPageChrome)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx new file mode 100644 index 0000000000000..fcb3b399c75b0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiSpacer, EuiTitle, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import { ENTERPRISE_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { SetupGuide as SetupGuideLayout } from '../../../shared/setup_guide'; +import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; +import GettingStarted from './assets/getting_started.png'; + +export const SetupGuide: React.FC = () => ( + + + + + + {i18n.translate('xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt', + + + +

+ +

+
+ + +

+ +

+
+
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx index b2918dac086f6..2c0902163e3d6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import '../__mocks__/shallow_usecontext.mock'; + +import React, { useContext } from 'react'; import { shallow } from 'enzyme'; import { EuiPage } from '@elastic/eui'; @@ -12,54 +14,31 @@ import '../__mocks__/kea.mock'; import { useValues } from 'kea'; import { EnterpriseSearch } from './'; +import { SetupGuide } from './components/setup_guide'; import { ErrorConnecting } from './components/error_connecting'; -import { ProductCard } from './components/product_card'; +import { ProductSelector } from './components/product_selector'; describe('EnterpriseSearch', () => { beforeEach(() => { (useValues as jest.Mock).mockReturnValue({ errorConnecting: false }); + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); }); - it('renders the overview page and product cards', () => { - const wrapper = shallow( - - ); + it('renders the Setup Guide and Product Selector', () => { + const wrapper = shallow(); - expect(wrapper.find(EuiPage).hasClass('enterpriseSearchOverview')).toBe(true); - expect(wrapper.find(ProductCard)).toHaveLength(2); + expect(wrapper.find(SetupGuide)).toHaveLength(1); + expect(wrapper.find(ProductSelector)).toHaveLength(1); }); - it('renders the error connecting prompt', () => { + it('renders the error connecting prompt when host is not configured', () => { (useValues as jest.Mock).mockReturnValueOnce({ errorConnecting: true }); + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: '' } })); + const wrapper = shallow(); expect(wrapper.find(ErrorConnecting)).toHaveLength(1); expect(wrapper.find(EuiPage)).toHaveLength(0); - }); - - describe('access checks', () => { - it('does not render the App Search card if the user does not have access to AS', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(ProductCard)).toHaveLength(1); - expect(wrapper.find(ProductCard).prop('product').ID).toEqual('workplaceSearch'); - }); - - it('does not render the Workplace Search card if the user does not have access to WS', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(ProductCard)).toHaveLength(1); - expect(wrapper.find(ProductCard).prop('product').ID).toEqual('appSearch'); - }); - - it('does not render any cards if the user does not have access', () => { - const wrapper = shallow(); - - expect(wrapper.find(ProductCard)).toHaveLength(0); - }); + expect(wrapper.find(ProductSelector)).toHaveLength(0); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx index 3a3ba02e07058..e2c05434dd0bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx @@ -4,81 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useContext } from 'react'; +import { Route, Switch } from 'react-router-dom'; import { useValues } from 'kea'; -import { - EuiPage, - EuiPageBody, - EuiPageHeader, - EuiPageHeaderSection, - EuiPageContentBody, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { KibanaContext, IKibanaContext } from '../index'; import { IInitialAppData } from '../../../common/types'; -import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants'; import { HttpLogic } from '../shared/http'; -import { SetEnterpriseSearchChrome as SetPageChrome } from '../shared/kibana_chrome'; -import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../shared/telemetry'; + +import { ROOT_PATH, SETUP_GUIDE_PATH } from './routes'; import { ErrorConnecting } from './components/error_connecting'; -import { ProductCard } from './components/product_card'; +import { ProductSelector } from './components/product_selector'; +import { SetupGuide } from './components/setup_guide'; -import AppSearchImage from './assets/app_search.png'; -import WorkplaceSearchImage from './assets/workplace_search.png'; import './index.scss'; export const EnterpriseSearch: React.FC = ({ access = {} }) => { const { errorConnecting } = useValues(HttpLogic); - const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access; - - return errorConnecting ? ( - - ) : ( - - - - - - - - -

- {i18n.translate('xpack.enterpriseSearch.overview.heading', { - defaultMessage: 'Welcome to Elastic Enterprise Search', - })} -

-
- -

- {i18n.translate('xpack.enterpriseSearch.overview.subheading', { - defaultMessage: 'Select a product to get started', - })} -

-
-
-
- - - {hasAppSearchAccess && ( - - - - )} - {hasWorkplaceSearchAccess && ( - - - - )} - - - -
-
+ const { config } = useContext(KibanaContext) as IKibanaContext; + + const showErrorConnecting = config.host && errorConnecting; + + return ( + + + + + + {showErrorConnecting ? : } + + ); }; diff --git a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/routes.ts similarity index 75% rename from x-pack/legacy/server/lib/create_router/call_with_request_factory/index.js rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search/routes.ts index 787814d87dff9..1f9c06e9683ab 100644 --- a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.js +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/routes.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { callWithRequestFactory } from './call_with_request_factory'; +export const ROOT_PATH = '/'; +export const SETUP_GUIDE_PATH = '/setup_guide'; diff --git a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx index 053c450ab925e..66772f96671e8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx @@ -6,7 +6,6 @@ import React from 'react'; -import { AppMountParameters } from 'src/core/public'; import { coreMock } from 'src/core/public/mocks'; import { licensingMock } from '../../../licensing/public/mocks'; @@ -15,37 +14,38 @@ import { AppSearch } from './app_search'; import { WorkplaceSearch } from './workplace_search'; describe('renderApp', () => { - let params: AppMountParameters; - const core = coreMock.createStart(); - const plugins = { - licensing: licensingMock.createSetup(), + const kibanaDeps = { + params: coreMock.createAppMountParamters(), + core: coreMock.createStart(), + plugins: { licensing: licensingMock.createStart() }, + } as any; + const pluginData = { + config: {}, + data: {}, } as any; - const config = {}; - const data = {} as any; beforeEach(() => { jest.clearAllMocks(); - params = coreMock.createAppMountParamters(); }); it('mounts and unmounts UI', () => { const MockApp = () =>
Hello world!
; - const unmount = renderApp(MockApp, params, core, plugins, config, data); - expect(params.element.querySelector('.hello-world')).not.toBeNull(); + const unmount = renderApp(MockApp, kibanaDeps, pluginData); + expect(kibanaDeps.params.element.querySelector('.hello-world')).not.toBeNull(); unmount(); - expect(params.element.innerHTML).toEqual(''); + expect(kibanaDeps.params.element.innerHTML).toEqual(''); }); it('renders AppSearch', () => { - renderApp(AppSearch, params, core, plugins, config, data); - expect(params.element.querySelector('.setupGuide')).not.toBeNull(); + renderApp(AppSearch, kibanaDeps, pluginData); + expect(kibanaDeps.params.element.querySelector('.setupGuide')).not.toBeNull(); }); it('renders WorkplaceSearch', () => { - renderApp(WorkplaceSearch, params, core, plugins, config, data); - expect(params.element.querySelector('.setupGuide')).not.toBeNull(); + renderApp(WorkplaceSearch, kibanaDeps, pluginData); + expect(kibanaDeps.params.element.querySelector('.setupGuide')).not.toBeNull(); }); }); @@ -54,7 +54,7 @@ describe('renderHeaderActions', () => { const mockHeaderEl = document.createElement('header'); const MockHeaderActions = () => ; - const unmount = renderHeaderActions(MockHeaderActions, mockHeaderEl, {} as any); + const unmount = renderHeaderActions(MockHeaderActions, mockHeaderEl); expect(mockHeaderEl.querySelector('.hello-world')).not.toBeNull(); unmount(); diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 43056f2f65538..2c6bc787923e3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -13,24 +13,16 @@ import { Store } from 'redux'; import { getContext, resetContext } from 'kea'; import { I18nProvider } from '@kbn/i18n/react'; -import { - AppMountParameters, - CoreStart, - ApplicationStart, - HttpSetup, - ChromeBreadcrumb, -} from 'src/core/public'; -import { ClientConfigType, ClientData, PluginsSetup } from '../plugin'; -import { LicenseProvider } from './shared/licensing'; -import { FlashMessagesProvider } from './shared/flash_messages'; -import { HttpProvider } from './shared/http'; -import { IExternalUrl } from './shared/enterprise_search_url'; +import { AppMountParameters, CoreStart, ApplicationStart, ChromeBreadcrumb } from 'src/core/public'; +import { PluginsStart, ClientConfigType, ClientData } from '../plugin'; +import { mountLicensingLogic } from './shared/licensing'; +import { mountHttpLogic } from './shared/http'; +import { mountFlashMessagesLogic } from './shared/flash_messages'; +import { externalUrl } from './shared/enterprise_search_url'; import { IInitialAppData } from '../../common/types'; export interface IKibanaContext { config: { host?: string }; - externalUrl: IExternalUrl; - http: HttpSetup; navigateToUrl: ApplicationStart['navigateToUrl']; setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void; setDocTitle(title: string): void; @@ -46,46 +38,51 @@ export const KibanaContext = React.createContext({}); export const renderApp = ( App: React.FC, - params: AppMountParameters, - core: CoreStart, - plugins: PluginsSetup, - config: ClientConfigType, - { externalUrl, errorConnecting, ...initialData }: ClientData + { params, core, plugins }: { params: AppMountParameters; core: CoreStart; plugins: PluginsStart }, + { config, data }: { config: ClientConfigType; data: ClientData } ) => { + const { publicUrl, errorConnecting, ...initialData } = data; + externalUrl.enterpriseSearchUrl = publicUrl || config.host || ''; + resetContext({ createStore: true }); const store = getContext().store as Store; + const unmountLicensingLogic = mountLicensingLogic({ + license$: plugins.licensing.license$, + }); + + const unmountHttpLogic = mountHttpLogic({ + http: core.http, + errorConnecting, + readOnlyMode: initialData.readOnlyMode, + }); + + const unmountFlashMessagesLogic = mountFlashMessagesLogic({ history: params.history }); + ReactDOM.render( - - - - - - - - - + + + + + , params.element ); return () => { ReactDOM.unmountComponentAtNode(params.element); + unmountLicensingLogic(); + unmountHttpLogic(); + unmountFlashMessagesLogic(); }; }; @@ -95,15 +92,8 @@ export const renderApp = ( * a custom HeaderActions component (e.g., WorkplaceSearchHeaderActions) * @see https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md */ -interface IHeaderActionsProps { - externalUrl: IExternalUrl; -} -export const renderHeaderActions = ( - HeaderActions: React.FC, - kibanaHeaderEl: HTMLElement, - externalUrl: IExternalUrl -) => { - ReactDOM.render(, kibanaHeaderEl); +export const renderHeaderActions = (HeaderActions: React.FC, kibanaHeaderEl: HTMLElement) => { + ReactDOM.render(, kibanaHeaderEl); return () => ReactDOM.unmountComponentAtNode(kibanaHeaderEl); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.test.ts new file mode 100644 index 0000000000000..55c4f465d9ed4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.test.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { externalUrl, getEnterpriseSearchUrl, getAppSearchUrl, getWorkplaceSearchUrl } from './'; + +describe('Enterprise Search external URL helpers', () => { + describe('getter/setter tests', () => { + it('defaults to an empty string', () => { + expect(externalUrl.enterpriseSearchUrl).toEqual(''); + }); + + it('sets the internal enterpriseSearchUrl value', () => { + externalUrl.enterpriseSearchUrl = 'http://localhost:3002'; + expect(externalUrl.enterpriseSearchUrl).toEqual('http://localhost:3002'); + }); + + it('does not allow mutating enterpriseSearchUrl once set', () => { + externalUrl.enterpriseSearchUrl = 'hello world'; + expect(externalUrl.enterpriseSearchUrl).toEqual('http://localhost:3002'); + }); + }); + + describe('function helpers', () => { + it('generates a public Enterprise Search URL', () => { + expect(getEnterpriseSearchUrl()).toEqual('http://localhost:3002'); + expect(getEnterpriseSearchUrl('/login')).toEqual('http://localhost:3002/login'); + }); + + it('generates a public App Search URL', () => { + expect(getAppSearchUrl()).toEqual('http://localhost:3002/as'); + expect(getAppSearchUrl('/path')).toEqual('http://localhost:3002/as/path'); + }); + + it('generates a public Workplace Search URL', () => { + expect(getWorkplaceSearchUrl()).toEqual('http://localhost:3002/ws'); + expect(getWorkplaceSearchUrl('/path')).toEqual('http://localhost:3002/ws/path'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.ts new file mode 100644 index 0000000000000..80b506f31ad61 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * NOTE: The externalUrl obj holds the reference to externalUrl, which should + * only ever be updated once on plugin init. We're using a getter and setter + * here to ensure it isn't accidentally mutated. + * + * Someday (8.x+), when our UI is entirely on Kibana and no longer on + * Enterprise Search's standalone UI, we can potentially deprecate this helper. + */ +export const externalUrl = { + _enterpriseSearchUrl: '', + get enterpriseSearchUrl() { + return this._enterpriseSearchUrl; + }, + set enterpriseSearchUrl(value) { + if (this._enterpriseSearchUrl) { + // enterpriseSearchUrl is set once on plugin init - we should not mutate it + return; + } + this._enterpriseSearchUrl = value; + }, +}; + +export const getEnterpriseSearchUrl = (path: string = ''): string => { + return externalUrl.enterpriseSearchUrl + path; +}; +export const getAppSearchUrl = (path: string = ''): string => { + return getEnterpriseSearchUrl('/as' + path); +}; +export const getWorkplaceSearchUrl = (path: string = ''): string => { + return getEnterpriseSearchUrl('/ws' + path); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.test.ts deleted file mode 100644 index 1092c88cbbc11..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ExternalUrl } from './'; - -describe('Enterprise Search external URL helper', () => { - const externalUrl = new ExternalUrl('http://localhost:3002'); - - it('exposes a public enterpriseSearchUrl string', () => { - expect(externalUrl.enterpriseSearchUrl).toEqual('http://localhost:3002'); - }); - - it('generates a public App Search URL', () => { - expect(externalUrl.getAppSearchUrl()).toEqual('http://localhost:3002/as'); - expect(externalUrl.getAppSearchUrl('/path')).toEqual('http://localhost:3002/as/path'); - }); - - it('generates a public Workplace Search URL', () => { - expect(externalUrl.getWorkplaceSearchUrl()).toEqual('http://localhost:3002/ws'); - expect(externalUrl.getWorkplaceSearchUrl('/path')).toEqual('http://localhost:3002/ws/path'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.ts deleted file mode 100644 index 9db48d197f3bc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.ts +++ /dev/null @@ -1,38 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * Small helper for generating external public-facing URLs - * to the legacy/standalone Enterprise Search app - */ -export interface IExternalUrl { - enterpriseSearchUrl?: string; - getAppSearchUrl(path?: string): string; - getWorkplaceSearchUrl(path?: string): string; -} - -export class ExternalUrl { - public enterpriseSearchUrl: string; - - constructor(externalUrl: string) { - this.enterpriseSearchUrl = externalUrl; - - this.getAppSearchUrl = this.getAppSearchUrl.bind(this); - this.getWorkplaceSearchUrl = this.getWorkplaceSearchUrl.bind(this); - } - - private getExternalUrl(path: string): string { - return this.enterpriseSearchUrl + path; - } - - public getAppSearchUrl(path: string = ''): string { - return this.getExternalUrl('/as' + path); - } - - public getWorkplaceSearchUrl(path: string = ''): string { - return this.getExternalUrl('/ws' + path); - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts index d2d82a43c6dd9..177d8e0535c72 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts @@ -4,4 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ExternalUrl, IExternalUrl } from './generate_external_url'; +export { + externalUrl, + getEnterpriseSearchUrl, + getAppSearchUrl, + getWorkplaceSearchUrl, +} from './external_url'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.test.ts index 136912847baa9..c12011b47a472 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.test.ts @@ -6,23 +6,25 @@ import { resetContext } from 'kea'; -import { FlashMessagesLogic, IFlashMessage } from './flash_messages_logic'; +import { mockHistory } from '../../__mocks__'; + +import { FlashMessagesLogic, mountFlashMessagesLogic, IFlashMessage } from './'; describe('FlashMessagesLogic', () => { - const DEFAULT_VALUES = { - messages: [], - queuedMessages: [], - historyListener: null, - }; + const mount = () => mountFlashMessagesLogic({ history: mockHistory as any }); beforeEach(() => { jest.clearAllMocks(); resetContext({}); }); - it('has expected default values', () => { - FlashMessagesLogic.mount(); - expect(FlashMessagesLogic.values).toEqual(DEFAULT_VALUES); + it('has default values', () => { + mount(); + expect(FlashMessagesLogic.values).toEqual({ + messages: [], + queuedMessages: [], + historyListener: expect.any(Function), + }); }); describe('setFlashMessages()', () => { @@ -33,7 +35,7 @@ describe('FlashMessagesLogic', () => { { type: 'info', message: 'Everything is fine, nothing is ruined' }, ]; - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setFlashMessages(messages); expect(FlashMessagesLogic.values.messages).toEqual(messages); @@ -42,7 +44,7 @@ describe('FlashMessagesLogic', () => { it('automatically converts to an array if a single message obj is passed in', () => { const message = { type: 'success', message: 'I turn into an array!' } as IFlashMessage; - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setFlashMessages(message); expect(FlashMessagesLogic.values.messages).toEqual([message]); @@ -51,7 +53,7 @@ describe('FlashMessagesLogic', () => { describe('clearFlashMessages()', () => { it('sets messages back to an empty array', () => { - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setFlashMessages('test' as any); FlashMessagesLogic.actions.clearFlashMessages(); @@ -63,7 +65,7 @@ describe('FlashMessagesLogic', () => { it('sets an array of messages', () => { const queuedMessage: IFlashMessage = { type: 'error', message: 'You deleted a thing' }; - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setQueuedMessages(queuedMessage); expect(FlashMessagesLogic.values.queuedMessages).toEqual([queuedMessage]); @@ -72,7 +74,7 @@ describe('FlashMessagesLogic', () => { describe('clearQueuedMessages()', () => { it('sets queued messages back to an empty array', () => { - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setQueuedMessages('test' as any); FlashMessagesLogic.actions.clearQueuedMessages(); @@ -83,30 +85,25 @@ describe('FlashMessagesLogic', () => { describe('history listener logic', () => { describe('setHistoryListener()', () => { it('sets the historyListener value', () => { - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setHistoryListener('test' as any); expect(FlashMessagesLogic.values.historyListener).toEqual('test'); }); }); - describe('listenToHistory()', () => { + describe('on mount', () => { it('listens for history changes and clears messages on change', () => { - FlashMessagesLogic.mount(); + mount(); + expect(mockHistory.listen).toHaveBeenCalled(); + FlashMessagesLogic.actions.setQueuedMessages(['queuedMessages'] as any); jest.spyOn(FlashMessagesLogic.actions, 'clearFlashMessages'); jest.spyOn(FlashMessagesLogic.actions, 'setFlashMessages'); jest.spyOn(FlashMessagesLogic.actions, 'clearQueuedMessages'); jest.spyOn(FlashMessagesLogic.actions, 'setHistoryListener'); - const mockListener = jest.fn(() => jest.fn()); - const history = { listen: mockListener } as any; - FlashMessagesLogic.actions.listenToHistory(history); - - expect(mockListener).toHaveBeenCalled(); - expect(FlashMessagesLogic.actions.setHistoryListener).toHaveBeenCalled(); - - const mockHistoryChange = (mockListener.mock.calls[0] as any)[0]; + const mockHistoryChange = (mockHistory.listen.mock.calls[0] as any)[0]; mockHistoryChange(); expect(FlashMessagesLogic.actions.clearFlashMessages).toHaveBeenCalled(); expect(FlashMessagesLogic.actions.setFlashMessages).toHaveBeenCalledWith([ @@ -116,19 +113,20 @@ describe('FlashMessagesLogic', () => { }); }); - describe('beforeUnmount', () => { - it('removes history listener on unmount', () => { + describe('on unmount', () => { + it('removes history listener', () => { const mockUnlistener = jest.fn(); - const unmount = FlashMessagesLogic.mount(); + mockHistory.listen.mockReturnValueOnce(mockUnlistener); - FlashMessagesLogic.actions.setHistoryListener(mockUnlistener); + const unmount = mount(); unmount(); expect(mockUnlistener).toHaveBeenCalled(); }); it('does not crash if no listener exists', () => { - const unmount = FlashMessagesLogic.mount(); + const unmount = mount(); + FlashMessagesLogic.actions.setHistoryListener(null as any); unmount(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts index 37a8f16acad6d..1735cc8ac7228 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts @@ -24,7 +24,6 @@ export interface IFlashMessagesActions { clearFlashMessages(): void; setQueuedMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] }; clearQueuedMessages(): void; - listenToHistory(history: History): History; setHistoryListener(historyListener: Function): { historyListener: Function }; } @@ -38,7 +37,6 @@ export const FlashMessagesLogic = kea null, setQueuedMessages: (messages) => ({ messages: convertToArray(messages) }), clearQueuedMessages: () => null, - listenToHistory: (history) => history, setHistoryListener: (historyListener) => ({ historyListener }), }, reducers: { @@ -63,21 +61,31 @@ export const FlashMessagesLogic = kea ({ - listenToHistory: (history) => { + events: ({ props, values, actions }) => ({ + afterMount: () => { // On React Router navigation, clear previous flash messages and load any queued messages - const unlisten = history.listen(() => { + const unlisten = props.history.listen(() => { actions.clearFlashMessages(); actions.setFlashMessages(values.queuedMessages); actions.clearQueuedMessages(); }); actions.setHistoryListener(unlisten); }, - }), - events: ({ values }) => ({ beforeUnmount: () => { const { historyListener: removeHistoryListener } = values; if (removeHistoryListener) removeHistoryListener(); }, }), }); + +/** + * Mount/props helper + */ +interface IFlashMessagesLogicProps { + history: History; +} +export const mountFlashMessagesLogic = (props: IFlashMessagesLogicProps) => { + FlashMessagesLogic(props); + const unmount = FlashMessagesLogic.mount(); + return unmount; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.test.tsx deleted file mode 100644 index bcd7abd6d7ce2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.test.tsx +++ /dev/null @@ -1,46 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import '../../__mocks__/shallow_usecontext.mock'; -import '../../__mocks__/kea.mock'; - -import React from 'react'; -import { shallow } from 'enzyme'; -import { useValues, useActions } from 'kea'; - -import { mockHistory } from '../../__mocks__'; - -import { FlashMessagesProvider } from './'; - -describe('FlashMessagesProvider', () => { - const props = { history: mockHistory as any }; - const listenToHistory = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - (useActions as jest.Mock).mockImplementationOnce(() => ({ listenToHistory })); - }); - - it('does not render', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('listens to history on mount', () => { - shallow(); - - expect(listenToHistory).toHaveBeenCalledWith(mockHistory); - }); - - it('does not add another history listener if one already exists', () => { - (useValues as jest.Mock).mockImplementationOnce(() => ({ historyListener: 'exists' as any })); - - shallow(); - - expect(listenToHistory).not.toHaveBeenCalledWith(props); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.tsx deleted file mode 100644 index a3ceabcf6ac8a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useEffect } from 'react'; -import { useValues, useActions } from 'kea'; -import { History } from 'history'; - -import { FlashMessagesLogic } from './flash_messages_logic'; - -interface IFlashMessagesProviderProps { - history: History; -} - -export const FlashMessagesProvider: React.FC = ({ history }) => { - const { historyListener } = useValues(FlashMessagesLogic); - const { listenToHistory } = useActions(FlashMessagesLogic); - - useEffect(() => { - if (!historyListener) listenToHistory(history); - }, []); - - return null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts index c4daeb44420c8..21c1a60efa6b7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts @@ -10,7 +10,7 @@ export { IFlashMessage, IFlashMessagesValues, IFlashMessagesActions, + mountFlashMessagesLogic, } from './flash_messages_logic'; -export { FlashMessagesProvider } from './flash_messages_provider'; export { flashAPIErrors } from './handle_api_errors'; export { setSuccessMessage, setErrorMessage, setQueuedSuccessMessage } from './set_message_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts index c3c60d77f4577..f2ddd560ac9c1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { mockHistory } from '../../__mocks__'; + import { FlashMessagesLogic, + mountFlashMessagesLogic, setSuccessMessage, setErrorMessage, setQueuedSuccessMessage, @@ -15,7 +18,7 @@ describe('Flash Message Helpers', () => { const message = 'I am a message'; beforeEach(() => { - FlashMessagesLogic.mount(); + mountFlashMessagesLogic({ history: mockHistory as any }); }); it('setSuccessMessage()', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts index b65499be2f7c0..df32b5496c367 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts @@ -8,31 +8,20 @@ import { resetContext } from 'kea'; import { httpServiceMock } from 'src/core/public/mocks'; -import { HttpLogic } from './http_logic'; +import { HttpLogic, mountHttpLogic } from './http_logic'; describe('HttpLogic', () => { const mockHttp = httpServiceMock.createSetupContract(); - const DEFAULT_VALUES = { - http: null, - httpInterceptors: [], - errorConnecting: false, - readOnlyMode: false, - }; + const mount = () => mountHttpLogic({ http: mockHttp }); beforeEach(() => { jest.clearAllMocks(); resetContext({}); }); - it('has expected default values', () => { - HttpLogic.mount(); - expect(HttpLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('initializeHttp()', () => { - it('sets values based on passed props', () => { - HttpLogic.mount(); - HttpLogic.actions.initializeHttp({ + describe('mounts', () => { + it('sets values from props', () => { + mountHttpLogic({ http: mockHttp, errorConnecting: true, readOnlyMode: true, @@ -40,7 +29,7 @@ describe('HttpLogic', () => { expect(HttpLogic.values).toEqual({ http: mockHttp, - httpInterceptors: [], + httpInterceptors: expect.any(Array), errorConnecting: true, readOnlyMode: true, }); @@ -49,7 +38,9 @@ describe('HttpLogic', () => { describe('setErrorConnecting()', () => { it('sets errorConnecting value', () => { - HttpLogic.mount(); + mount(); + expect(HttpLogic.values.errorConnecting).toEqual(false); + HttpLogic.actions.setErrorConnecting(true); expect(HttpLogic.values.errorConnecting).toEqual(true); @@ -60,7 +51,9 @@ describe('HttpLogic', () => { describe('setReadOnlyMode()', () => { it('sets readOnlyMode value', () => { - HttpLogic.mount(); + mount(); + expect(HttpLogic.values.readOnlyMode).toEqual(false); + HttpLogic.actions.setReadOnlyMode(true); expect(HttpLogic.values.readOnlyMode).toEqual(true); @@ -72,10 +65,8 @@ describe('HttpLogic', () => { describe('http interceptors', () => { describe('initializeHttpInterceptors()', () => { beforeEach(() => { - HttpLogic.mount(); + mount(); jest.spyOn(HttpLogic.actions, 'setHttpInterceptors'); - HttpLogic.actions.initializeHttp({ http: mockHttp }); - HttpLogic.actions.initializeHttpInterceptors(); }); it('calls http.intercept and sets an array of interceptors', () => { @@ -165,7 +156,7 @@ describe('HttpLogic', () => { }); it('sets httpInterceptors and calls all valid remove functions on unmount', () => { - const unmount = HttpLogic.mount(); + const unmount = mount(); const httpInterceptors = [jest.fn(), undefined, jest.fn()] as any; HttpLogic.actions.setHttpInterceptors(httpInterceptors); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts index 72380142fe399..d16e507bfb3bc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts @@ -7,7 +7,6 @@ import { kea, MakeLogicType } from 'kea'; import { HttpSetup, HttpInterceptorResponseError, HttpResponse } from 'src/core/public'; -import { IHttpProviderProps } from './http_provider'; import { READ_ONLY_MODE_HEADER } from '../../../../common/constants'; @@ -18,7 +17,6 @@ export interface IHttpValues { readOnlyMode: boolean; } export interface IHttpActions { - initializeHttp({ http, errorConnecting, readOnlyMode }: IHttpProviderProps): IHttpProviderProps; initializeHttpInterceptors(): void; setHttpInterceptors(httpInterceptors: Function[]): { httpInterceptors: Function[] }; setErrorConnecting(errorConnecting: boolean): { errorConnecting: boolean }; @@ -28,19 +26,13 @@ export interface IHttpActions { export const HttpLogic = kea>({ path: ['enterprise_search', 'http_logic'], actions: { - initializeHttp: (props) => props, initializeHttpInterceptors: () => null, setHttpInterceptors: (httpInterceptors) => ({ httpInterceptors }), setErrorConnecting: (errorConnecting) => ({ errorConnecting }), setReadOnlyMode: (readOnlyMode) => ({ readOnlyMode }), }, - reducers: { - http: [ - (null as unknown) as HttpSetup, - { - initializeHttp: (_, { http }) => http, - }, - ], + reducers: ({ props }) => ({ + http: [props.http, {}], httpInterceptors: [ [], { @@ -48,20 +40,18 @@ export const HttpLogic = kea>({ }, ], errorConnecting: [ - false, + props.errorConnecting || false, { - initializeHttp: (_, { errorConnecting }) => !!errorConnecting, setErrorConnecting: (_, { errorConnecting }) => errorConnecting, }, ], readOnlyMode: [ - false, + props.readOnlyMode || false, { - initializeHttp: (_, { readOnlyMode }) => !!readOnlyMode, setReadOnlyMode: (_, { readOnlyMode }) => readOnlyMode, }, ], - }, + }), listeners: ({ values, actions }) => ({ initializeHttpInterceptors: () => { const httpInterceptors = []; @@ -103,7 +93,10 @@ export const HttpLogic = kea>({ actions.setHttpInterceptors(httpInterceptors); }, }), - events: ({ values }) => ({ + events: ({ values, actions }) => ({ + afterMount: () => { + actions.initializeHttpInterceptors(); + }, beforeUnmount: () => { values.httpInterceptors.forEach((removeInterceptorFn?: Function) => { if (removeInterceptorFn) removeInterceptorFn(); @@ -112,6 +105,20 @@ export const HttpLogic = kea>({ }), }); +/** + * Mount/props helper + */ +interface IHttpLogicProps { + http: HttpSetup; + errorConnecting?: boolean; + readOnlyMode?: boolean; +} +export const mountHttpLogic = (props: IHttpLogicProps) => { + HttpLogic(props); + const unmount = HttpLogic.mount(); + return unmount; +}; + /** * Small helper that checks whether or not an http call is for an Enterprise Search API */ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.test.tsx deleted file mode 100644 index 902c910f10d7c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.test.tsx +++ /dev/null @@ -1,45 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import '../../__mocks__/shallow_usecontext.mock'; -import '../../__mocks__/kea.mock'; - -import React from 'react'; -import { shallow } from 'enzyme'; -import { useActions } from 'kea'; - -import { HttpProvider } from './'; - -describe('HttpProvider', () => { - const props = { - http: {} as any, - errorConnecting: false, - readOnlyMode: false, - }; - const initializeHttp = jest.fn(); - const initializeHttpInterceptors = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - (useActions as jest.Mock).mockImplementationOnce(() => ({ - initializeHttp, - initializeHttpInterceptors, - })); - }); - - it('does not render', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('calls initialization actions on mount', () => { - shallow(); - - expect(initializeHttp).toHaveBeenCalledWith(props); - expect(initializeHttpInterceptors).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.tsx deleted file mode 100644 index db1b0d611079a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.tsx +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useEffect } from 'react'; -import { useActions } from 'kea'; - -import { HttpSetup } from 'src/core/public'; - -import { HttpLogic } from './http_logic'; - -export interface IHttpProviderProps { - http: HttpSetup; - errorConnecting?: boolean; - readOnlyMode?: boolean; -} - -export const HttpProvider: React.FC = (props) => { - const { initializeHttp, initializeHttpInterceptors } = useActions(HttpLogic); - - useEffect(() => { - initializeHttp(props); - initializeHttpInterceptors(); - }, []); - - return null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts index db65e80ca25c2..46a52415f8564 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { HttpLogic, IHttpValues, IHttpActions } from './http_logic'; -export { HttpProvider } from './http_provider'; +export { HttpLogic, IHttpValues, IHttpActions, mountHttpLogic } from './http_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts index 29c11ffa1cef8..4e371b337c40a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { LicenseContext, LicenseProvider, ILicenseContext } from './license_context'; -export { hasPlatinumLicense, hasGoldLicense } from './license_checks'; +export { LicensingLogic, mountLicensingLogic } from './licensing_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts deleted file mode 100644 index 40f0f6380c21c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts +++ /dev/null @@ -1,54 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { hasPlatinumLicense, hasGoldLicense } from './license_checks'; - -describe('hasPlatinumLicense', () => { - it('is true for platinum licenses', () => { - expect(hasPlatinumLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true); - }); - - it('is true for enterprise licenses', () => { - expect(hasPlatinumLicense({ isActive: true, type: 'enterprise' } as any)).toEqual(true); - }); - - it('is true for trial licenses', () => { - expect(hasPlatinumLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true); - }); - - it('is false if the current license is expired', () => { - expect(hasPlatinumLicense({ isActive: false, type: 'platinum' } as any)).toEqual(false); - expect(hasPlatinumLicense({ isActive: false, type: 'enterprise' } as any)).toEqual(false); - expect(hasPlatinumLicense({ isActive: false, type: 'trial' } as any)).toEqual(false); - }); - - it('is false for licenses below platinum', () => { - expect(hasPlatinumLicense({ isActive: true, type: 'basic' } as any)).toEqual(false); - expect(hasPlatinumLicense({ isActive: false, type: 'standard' } as any)).toEqual(false); - expect(hasPlatinumLicense({ isActive: true, type: 'gold' } as any)).toEqual(false); - }); -}); - -describe('hasGoldLicense', () => { - it('is true for gold+ and trial licenses', () => { - expect(hasGoldLicense({ isActive: true, type: 'gold' } as any)).toEqual(true); - expect(hasGoldLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true); - expect(hasGoldLicense({ isActive: true, type: 'enterprise' } as any)).toEqual(true); - expect(hasGoldLicense({ isActive: true, type: 'trial' } as any)).toEqual(true); - }); - - it('is false if the current license is expired', () => { - expect(hasGoldLicense({ isActive: false, type: 'gold' } as any)).toEqual(false); - expect(hasGoldLicense({ isActive: false, type: 'platinum' } as any)).toEqual(false); - expect(hasGoldLicense({ isActive: false, type: 'enterprise' } as any)).toEqual(false); - expect(hasGoldLicense({ isActive: false, type: 'trial' } as any)).toEqual(false); - }); - - it('is false for licenses below gold', () => { - expect(hasGoldLicense({ isActive: true, type: 'basic' } as any)).toEqual(false); - expect(hasGoldLicense({ isActive: false, type: 'standard' } as any)).toEqual(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts deleted file mode 100644 index d13d0909243be..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts +++ /dev/null @@ -1,17 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ILicense } from '../../../../../licensing/public'; - -export const hasPlatinumLicense = (license?: ILicense) => { - const qualifyingLicenses = ['platinum', 'enterprise', 'trial']; - return license?.isActive && qualifyingLicenses.includes(license?.type as string); -}; - -export const hasGoldLicense = (license?: ILicense) => { - const qualifyingLicenses = ['gold', 'platinum', 'enterprise', 'trial']; - return license?.isActive && qualifyingLicenses.includes(license?.type as string); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx deleted file mode 100644 index c65474ec1f590..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx +++ /dev/null @@ -1,24 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useContext } from 'react'; - -import { mountWithContext } from '../../__mocks__'; -import { LicenseContext, ILicenseContext } from './'; - -describe('LicenseProvider', () => { - const MockComponent: React.FC = () => { - const { license } = useContext(LicenseContext) as ILicenseContext; - return
{license?.type}
; - }; - - it('renders children', () => { - const wrapper = mountWithContext(, { license: { type: 'basic' } }); - - expect(wrapper.find('.license-test')).toHaveLength(1); - expect(wrapper.text()).toEqual('basic'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx deleted file mode 100644 index 9b47959ff7544..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import useObservable from 'react-use/lib/useObservable'; -import { Observable } from 'rxjs'; - -import { ILicense } from '../../../../../licensing/public'; - -export interface ILicenseContext { - license: ILicense; -} -interface ILicenseContextProps { - license$: Observable; - children: React.ReactNode; -} - -export const LicenseContext = React.createContext({}); - -export const LicenseProvider: React.FC = ({ license$, children }) => { - // Listen for changes to license subscription - const license = useObservable(license$); - - // Render rest of application and pass down license via context - return ; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.test.ts new file mode 100644 index 0000000000000..153a5ae765468 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.test.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resetContext } from 'kea'; +import { BehaviorSubject } from 'rxjs'; + +import { licensingMock } from '../../../../../licensing/public/mocks'; + +import { LicensingLogic, mountLicensingLogic } from './licensing_logic'; + +describe('LicensingLogic', () => { + const mockLicense = licensingMock.createLicense(); + const mockLicense$ = new BehaviorSubject(mockLicense); + const mount = () => mountLicensingLogic({ license$: mockLicense$ }); + + beforeEach(() => { + jest.clearAllMocks(); + resetContext({}); + }); + + describe('setLicense()', () => { + it('sets license value', () => { + mount(); + LicensingLogic.actions.setLicense('test' as any); + expect(LicensingLogic.values.license).toEqual('test'); + }); + }); + + describe('setLicenseSubscription()', () => { + it('sets licenseSubscription value', () => { + mount(); + LicensingLogic.actions.setLicenseSubscription('test' as any); + expect(LicensingLogic.values.licenseSubscription).toEqual('test'); + }); + }); + + describe('licensing subscription', () => { + describe('on mount', () => { + it('subscribes to the license observable', () => { + mount(); + expect(LicensingLogic.values.license).toEqual(mockLicense); + expect(LicensingLogic.values.licenseSubscription).not.toBeNull(); + }); + }); + + describe('on subscription update', () => { + it('updates the license value', () => { + mount(); + + const nextMockLicense = licensingMock.createLicense({ license: { status: 'invalid' } }); + mockLicense$.next(nextMockLicense); + + expect(LicensingLogic.values.license).toEqual(nextMockLicense); + }); + }); + + describe('on unmount', () => { + it('unsubscribes to the license observable', () => { + const mockUnsubscribe = jest.fn(); + const unmount = mountLicensingLogic({ + license$: { subscribe: () => ({ unsubscribe: mockUnsubscribe }) } as any, + }); + unmount(); + expect(mockUnsubscribe).toHaveBeenCalled(); + }); + + it('does not crash if no subscription exists', () => { + const unmount = mount(); + LicensingLogic.actions.setLicenseSubscription(null as any); + unmount(); + }); + }); + }); + + describe('license check selectors', () => { + beforeEach(() => { + mount(); + }); + + const updateLicense = (license: any) => { + const updatedLicense = licensingMock.createLicense({ license }); + mockLicense$.next(updatedLicense); + }; + + describe('hasPlatinumLicense', () => { + it('is true for platinum+ and trial licenses', () => { + updateLicense({ status: 'active', type: 'platinum' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(true); + + updateLicense({ status: 'active', type: 'enterprise' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(true); + + updateLicense({ status: 'active', type: 'trial' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(true); + }); + + it('is false if the current license is expired', () => { + updateLicense({ status: 'expired', type: 'platinum' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + + updateLicense({ status: 'expired', type: 'enterprise' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + + updateLicense({ status: 'expired', type: 'trial' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + }); + + it('is false for licenses below platinum', () => { + updateLicense({ status: 'active', type: 'basic' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + + updateLicense({ status: 'active', type: 'standard' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + + updateLicense({ status: 'active', type: 'gold' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + }); + }); + + describe('hasGoldLicense', () => { + it('is true for gold+ and trial licenses', () => { + updateLicense({ status: 'active', type: 'gold' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(true); + + updateLicense({ status: 'active', type: 'platinum' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(true); + + updateLicense({ status: 'active', type: 'enterprise' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(true); + + updateLicense({ status: 'active', type: 'trial' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(true); + }); + + it('is false if the current license is expired', () => { + updateLicense({ status: 'expired', type: 'gold' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + + updateLicense({ status: 'expired', type: 'platinum' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + + updateLicense({ status: 'expired', type: 'enterprise' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + + updateLicense({ status: 'expired', type: 'trial' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + }); + + it('is false for licenses below gold', () => { + updateLicense({ status: 'active', type: 'basic' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + + updateLicense({ status: 'active', type: 'standard' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.ts new file mode 100644 index 0000000000000..ae31b2ec6168a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { kea, MakeLogicType } from 'kea'; +import { Observable, Subscription } from 'rxjs'; + +import { ILicense } from '../../../../../licensing/public'; + +export interface ILicensingValues { + license: ILicense | null; + licenseSubscription: Subscription | null; + hasPlatinumLicense: boolean; + hasGoldLicense: boolean; +} +export interface ILicensingActions { + setLicense(license: ILicense): ILicense; + setLicenseSubscription(licenseSubscription: Subscription): Subscription; +} + +export const LicensingLogic = kea>({ + path: ['enterprise_search', 'licensing_logic'], + actions: { + setLicense: (license) => license, + setLicenseSubscription: (licenseSubscription) => licenseSubscription, + }, + reducers: { + license: [ + null, + { + setLicense: (_, license) => license, + }, + ], + licenseSubscription: [ + null, + { + setLicenseSubscription: (_, licenseSubscription) => licenseSubscription, + }, + ], + }, + selectors: { + hasPlatinumLicense: [ + (selectors) => [selectors.license], + (license) => { + const qualifyingLicenses = ['platinum', 'enterprise', 'trial']; + return license?.isActive && qualifyingLicenses.includes(license?.type); + }, + ], + hasGoldLicense: [ + (selectors) => [selectors.license], + (license) => { + const qualifyingLicenses = ['gold', 'platinum', 'enterprise', 'trial']; + return license?.isActive && qualifyingLicenses.includes(license?.type); + }, + ], + }, + events: ({ props, actions, values }) => ({ + afterMount: () => { + const licenseSubscription = props.license$.subscribe(async (license: ILicense) => { + actions.setLicense(license); + }); + actions.setLicenseSubscription(licenseSubscription); + }, + beforeUnmount: () => { + if (values.licenseSubscription) values.licenseSubscription.unsubscribe(); + }, + }), +}); + +/** + * Mount/props helper + */ +interface ILicensingLogicProps { + license$: Observable; +} +export const mountLicensingLogic = (props: ILicensingLogicProps) => { + LicensingLogic(props); + const unmount = LicensingLogic.mount(); + return unmount; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx index ce9071ad7b9d0..62c0af31cffd9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../__mocks__/shallow_usecontext.mock'; +import '../../__mocks__/kea.mock'; -import React, { useContext } from 'react'; +import React from 'react'; +import { useValues } from 'kea'; import { shallow } from 'enzyme'; import { EuiButton as EuiButtonExternal, EuiEmptyPrompt } from '@elastic/eui'; @@ -18,13 +19,6 @@ import { WorkplaceSearchLogo } from './assets/workplace_search_logo'; import { NotFound } from './'; describe('NotFound', () => { - const basicLicense = { isActive: true, type: 'basic' }; - const goldLicense = { isActive: true, type: 'gold' }; - - beforeEach(() => { - (useContext as jest.Mock).mockImplementation(() => ({ license: basicLicense })); - }); - it('renders an App Search 404 view', () => { const wrapper = shallow(); const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); @@ -50,7 +44,7 @@ describe('NotFound', () => { }); it('changes the support URL if the user has a gold+ license', () => { - (useContext as jest.Mock).mockImplementation(() => ({ license: goldLicense })); + (useValues as jest.Mock).mockReturnValueOnce({ hasGoldLicense: true }); const wrapper = shallow(); const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx index bd988854225fb..40bb5efcc6330 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; +import { useValues } from 'kea'; import { i18n } from '@kbn/i18n'; import { EuiPageContent, @@ -24,7 +25,7 @@ import { import { EuiButton } from '../react_router_helpers'; import { SetAppSearchChrome, SetWorkplaceSearchChrome } from '../kibana_chrome'; import { SendAppSearchTelemetry, SendWorkplaceSearchTelemetry } from '../telemetry'; -import { LicenseContext, ILicenseContext, hasGoldLicense } from '../licensing'; +import { LicensingLogic } from '../licensing'; import { AppSearchLogo } from './assets/app_search_logo'; import { WorkplaceSearchLogo } from './assets/workplace_search_logo'; @@ -39,8 +40,8 @@ interface NotFoundProps { } export const NotFound: React.FC = ({ product = {} }) => { - const { license } = useContext(LicenseContext) as ILicenseContext; - const supportUrl = hasGoldLicense(license) ? LICENSED_SUPPORT_URL : product.SUPPORT_URL; + const { hasGoldLicense } = useValues(LicensingLogic); + const supportUrl = hasGoldLicense ? LICENSED_SUPPORT_URL : product.SUPPORT_URL; let Logo; let SetPageChrome; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx index 1d64b453b2c2c..073c548ba47fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx @@ -4,11 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../__mocks__/kea.mock'; +import '../../__mocks__/shallow_usecontext.mock'; +import { mockHttpValues } from '../../__mocks__'; + import React from 'react'; +import { shallow } from 'enzyme'; -import { httpServiceMock } from 'src/core/public/mocks'; import { JSON_HEADER as headers } from '../../../../common/constants'; -import { mountWithKibanaContext } from '../../__mocks__'; import { sendTelemetry, @@ -18,8 +21,6 @@ import { } from './'; describe('Shared Telemetry Helpers', () => { - const httpMock = httpServiceMock.createSetupContract(); - beforeEach(() => { jest.clearAllMocks(); }); @@ -27,13 +28,13 @@ describe('Shared Telemetry Helpers', () => { describe('sendTelemetry', () => { it('successfully calls the server-side telemetry endpoint', () => { sendTelemetry({ - http: httpMock, + http: mockHttpValues.http, product: 'enterprise_search', action: 'viewed', metric: 'setup_guide', }); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { + expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"enterprise_search","action":"viewed","metric":"setup_guide"}', }); @@ -50,33 +51,27 @@ describe('Shared Telemetry Helpers', () => { describe('React component helpers', () => { it('SendEnterpriseSearchTelemetry component', () => { - mountWithKibanaContext(, { - http: httpMock, - }); + shallow(); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { + expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"enterprise_search","action":"viewed","metric":"page"}', }); }); it('SendAppSearchTelemetry component', () => { - mountWithKibanaContext(, { - http: httpMock, - }); + shallow(); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { + expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"app_search","action":"clicked","metric":"button"}', }); }); it('SendWorkplaceSearchTelemetry component', () => { - mountWithKibanaContext(, { - http: httpMock, - }); + shallow(); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { + expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"workplace_search","action":"error","metric":"not_found"}', }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx index e3c9ba9b8a218..2f87597897b41 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, useEffect } from 'react'; +import React, { useEffect } from 'react'; +import { useValues } from 'kea'; import { HttpSetup } from 'src/core/public'; import { JSON_HEADER as headers } from '../../../../common/constants'; -import { KibanaContext, IKibanaContext } from '../../index'; +import { HttpLogic } from '../http'; interface ISendTelemetryProps { action: 'viewed' | 'error' | 'clicked'; @@ -41,7 +42,7 @@ export const SendEnterpriseSearchTelemetry: React.FC = ({ action, metric, }) => { - const { http } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); useEffect(() => { sendTelemetry({ http, action, metric, product: 'enterprise_search' }); @@ -51,7 +52,7 @@ export const SendEnterpriseSearchTelemetry: React.FC = ({ }; export const SendAppSearchTelemetry: React.FC = ({ action, metric }) => { - const { http } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); useEffect(() => { sendTelemetry({ http, action, metric, product: 'app_search' }); @@ -61,7 +62,7 @@ export const SendAppSearchTelemetry: React.FC = ({ action, }; export const SendWorkplaceSearchTelemetry: React.FC = ({ action, metric }) => { - const { http } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); useEffect(() => { sendTelemetry({ http, action, metric, product: 'workplace_search' }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx index a006c5e3775d5..0ebd59eda5be7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx @@ -4,26 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ +import { externalUrl } from '../../../shared/enterprise_search_url'; + import React from 'react'; import { shallow } from 'enzyme'; - import { EuiButtonEmpty } from '@elastic/eui'; -import { ExternalUrl } from '../../../shared/enterprise_search_url'; import { WorkplaceSearchHeaderActions } from './'; describe('WorkplaceSearchHeaderActions', () => { - const externalUrl = new ExternalUrl('http://localhost:3002'); + it('does not render without an Enterprise Search URL set', () => { + const wrapper = shallow(); - it('renders a link to the search application', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiButtonEmpty).prop('href')).toEqual('http://localhost:3002/ws/search'); + expect(wrapper.isEmptyRender()).toBe(true); }); - it('does not render without an Enterprise Search host URL set', () => { - const wrapper = shallow(); + it('renders a link to the search application', () => { + externalUrl.enterpriseSearchUrl = 'http://localhost:3002'; - expect(wrapper.isEmptyRender()).toBe(true); + const wrapper = shallow(); + + expect(wrapper.find(EuiButtonEmpty).prop('href')).toEqual('http://localhost:3002/ws/search'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx index fa32d598f848d..b7da5b4281aa0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx @@ -8,15 +8,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty } from '@elastic/eui'; -import { IExternalUrl } from '../../../shared/enterprise_search_url'; +import { externalUrl, getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; -interface IProps { - externalUrl: IExternalUrl; -} - -export const WorkplaceSearchHeaderActions: React.FC = ({ externalUrl }) => { - const { enterpriseSearchUrl, getWorkplaceSearchUrl } = externalUrl; - if (!enterpriseSearchUrl) return null; +export const WorkplaceSearchHeaderActions: React.FC = () => { + if (!externalUrl.enterpriseSearchUrl) return null; return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx index 0e85d8467cff0..2553284744e4d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../__mocks__/shallow_usecontext.mock'; +import '../../../__mocks__/enterprise_search_url.mock'; + import React from 'react'; import { shallow } from 'enzyme'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx index 9fb627ed09791..5572716391112 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx @@ -3,13 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer } from '@elastic/eui'; import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { KibanaContext, IKibanaContext } from '../../../index'; +import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; import { SideNav, SideNavLink } from '../../../shared/layout'; import { @@ -22,10 +22,6 @@ import { } from '../../routes'; export const WorkplaceSearchNav: React.FC = () => { - const { - externalUrl: { getWorkplaceSearchUrl }, - } = useContext(KibanaContext) as IKibanaContext; - // TODO: icons return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.test.tsx index 429a2c509813d..2013b2609f33b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../../__mocks__/shallow_usecontext.mock'; +import '../../../../__mocks__/kea.mock'; import React from 'react'; import { shallow } from 'enzyme'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx index a914000654165..344b442d9a678 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx @@ -4,19 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; +import { useValues } from 'kea'; import { EuiButton, EuiButtonProps, EuiLinkProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { sendTelemetry } from '../../../../shared/telemetry'; -import { KibanaContext, IKibanaContext } from '../../../../index'; +import { HttpLogic } from '../../../../shared/http'; +import { getWorkplaceSearchUrl } from '../../../../shared/enterprise_search_url'; export const ProductButton: React.FC = () => { - const { - externalUrl: { getWorkplaceSearchUrl }, - http, - } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); const buttonProps = { fill: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx index ddbe327a40a30..67d435e330c3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx @@ -6,6 +6,8 @@ import React from 'react'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import _camelCase from 'lodash/camelCase'; import { images } from '../assets'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx index 17ca8e58a80fa..a2e252c886354 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx @@ -7,6 +7,8 @@ import React from 'react'; import classNames from 'classnames'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import _kebabCase from 'lodash/kebabCase'; import { Link } from 'react-router-dom'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.test.tsx index 1d7c565935e97..6be033d7225a8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.test.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../__mocks__/shallow_usecontext.mock'; +import '../../../__mocks__/kea.mock'; +import '../../../__mocks__/enterprise_search_url.mock'; import React from 'react'; import { shallow } from 'enzyme'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.tsx index 786357358dfa6..c1070d57f2856 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; +import { useValues } from 'kea'; import { EuiButton, @@ -17,8 +18,10 @@ import { EuiButtonEmptyProps, EuiLinkProps, } from '@elastic/eui'; + import { sendTelemetry } from '../../../shared/telemetry'; -import { KibanaContext, IKibanaContext } from '../../../index'; +import { HttpLogic } from '../../../shared/http'; +import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; interface IOnboardingCardProps { title: React.ReactNode; @@ -39,10 +42,7 @@ export const OnboardingCard: React.FC = ({ actionPath, complete, }) => { - const { - http, - externalUrl: { getWorkplaceSearchUrl }, - } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); const onClick = () => sendTelemetry({ diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx index 0f3eee074caef..37b3340b96a6a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../__mocks__/shallow_usecontext.mock'; import './__mocks__/overview_logic.mock'; import { setMockValues } from './__mocks__'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx index 0baadfc912ad5..132824833909d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useValues } from 'kea'; @@ -23,7 +23,8 @@ import { } from '@elastic/eui'; import sharedSourcesIcon from '../../components/shared/assets/share_circle.svg'; import { sendTelemetry } from '../../../shared/telemetry'; -import { KibanaContext, IKibanaContext } from '../../../index'; +import { HttpLogic } from '../../../shared/http'; +import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; import { ContentSection } from '../../components/shared/content_section'; @@ -135,10 +136,7 @@ export const OnboardingSteps: React.FC = () => { }; export const OrgNameOnboarding: React.FC = () => { - const { - http, - externalUrl: { getWorkplaceSearchUrl }, - } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); const onClick = () => sendTelemetry({ diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.test.tsx index 31613098f9fcc..989ff800483f6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.test.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../__mocks__/shallow_usecontext.mock'; import './__mocks__/overview_logic.mock'; import { setMockValues } from './__mocks__'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx index 0813999c9a078..d1b5228123d94 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; import moment from 'moment'; import { useValues } from 'kea'; @@ -14,7 +14,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { ContentSection } from '../../components/shared/content_section'; import { sendTelemetry } from '../../../shared/telemetry'; -import { KibanaContext, IKibanaContext } from '../../../index'; +import { HttpLogic } from '../../../shared/http'; +import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; import { SOURCE_DETAILS_PATH, getContentSourcePath } from '../../routes'; import { AppLogic } from '../../app_logic'; @@ -93,10 +94,7 @@ export const RecentActivityItem: React.FC = ({ timestamp, sourceId, }) => { - const { - http, - externalUrl: { getWorkplaceSearchUrl }, - } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); const onClick = () => sendTelemetry({ diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/statistic_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/statistic_card.test.tsx index edf266231b39e..013b23d2a9ec0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/statistic_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/statistic_card.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../__mocks__/shallow_usecontext.mock'; +import '../../../__mocks__/enterprise_search_url.mock'; import React from 'react'; import { shallow } from 'enzyme'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/statistic_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/statistic_card.tsx index 3e1d285698c0c..6c4f43b1a3a22 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/statistic_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/statistic_card.tsx @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; - +import React from 'react'; import { EuiCard, EuiFlexItem, EuiTitle, EuiTextColor } from '@elastic/eui'; -import { KibanaContext, IKibanaContext } from '../../../index'; +import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; interface IStatisticCardProps { title: string; @@ -17,10 +16,6 @@ interface IStatisticCardProps { } export const StatisticCard: React.FC = ({ title, count = 0, actionPath }) => { - const { - externalUrl: { getWorkplaceSearchUrl }, - } = useContext(KibanaContext) as IKibanaContext; - const linkProps = actionPath ? { href: getWorkplaceSearchUrl(actionPath), diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index c23bb23be3979..d870127f297b4 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -7,7 +7,6 @@ import { AppMountParameters, CoreSetup, - CoreStart, HttpSetup, Plugin, PluginInitializerContext, @@ -17,26 +16,28 @@ import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../src/plugins/home/public'; -import { LicensingPluginSetup } from '../../licensing/public'; +import { LicensingPluginStart } from '../../licensing/public'; import { APP_SEARCH_PLUGIN, ENTERPRISE_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, } from '../common/constants'; import { IInitialAppData } from '../common/types'; -import { ExternalUrl, IExternalUrl } from './applications/shared/enterprise_search_url'; +import { externalUrl } from './applications/shared/enterprise_search_url'; export interface ClientConfigType { host?: string; } export interface ClientData extends IInitialAppData { - externalUrl: IExternalUrl; + publicUrl?: string; errorConnecting?: boolean; } export interface PluginsSetup { home?: HomePublicPluginSetup; - licensing: LicensingPluginSetup; +} +export interface PluginsStart { + licensing: LicensingPluginStart; } export class EnterpriseSearchPlugin implements Plugin { @@ -46,7 +47,6 @@ export class EnterpriseSearchPlugin implements Plugin { constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); - this.data.externalUrl = new ExternalUrl(this.config.host || ''); } public setup(core: CoreSetup, plugins: PluginsSetup) { @@ -57,16 +57,17 @@ export class EnterpriseSearchPlugin implements Plugin { appRoute: ENTERPRISE_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { - const [coreStart] = await core.getStartServices(); - const { chrome } = coreStart; + const kibanaDeps = await this.getKibanaDeps(core, params); + const { chrome, http } = kibanaDeps.core; chrome.docTitle.change(ENTERPRISE_SEARCH_PLUGIN.NAME); - await this.getInitialData(coreStart.http); + await this.getInitialData(http); + const pluginData = this.getPluginData(); const { renderApp } = await import('./applications'); const { EnterpriseSearch } = await import('./applications/enterprise_search'); - return renderApp(EnterpriseSearch, params, coreStart, plugins, this.config, this.data); + return renderApp(EnterpriseSearch, kibanaDeps, pluginData); }, }); @@ -77,16 +78,17 @@ export class EnterpriseSearchPlugin implements Plugin { appRoute: APP_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { - const [coreStart] = await core.getStartServices(); - const { chrome } = coreStart; + const kibanaDeps = await this.getKibanaDeps(core, params); + const { chrome, http } = kibanaDeps.core; chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME); - await this.getInitialData(coreStart.http); + await this.getInitialData(http); + const pluginData = this.getPluginData(); const { renderApp } = await import('./applications'); const { AppSearch } = await import('./applications/app_search'); - return renderApp(AppSearch, params, coreStart, plugins, this.config, this.data); + return renderApp(AppSearch, kibanaDeps, pluginData); }, }); @@ -97,11 +99,12 @@ export class EnterpriseSearchPlugin implements Plugin { appRoute: WORKPLACE_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { - const [coreStart] = await core.getStartServices(); - const { chrome } = coreStart; - chrome.docTitle.change(WORKPLACE_SEARCH_PLUGIN.NAME); + const kibanaDeps = await this.getKibanaDeps(core, params); + const { chrome, http } = kibanaDeps.core; + chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME); - await this.getInitialData(coreStart.http); + await this.getInitialData(http); + const pluginData = this.getPluginData(); const { renderApp, renderHeaderActions } = await import('./applications'); const { WorkplaceSearch } = await import('./applications/workplace_search'); @@ -110,10 +113,10 @@ export class EnterpriseSearchPlugin implements Plugin { './applications/workplace_search/components/layout' ); params.setHeaderActionMenu((element) => - renderHeaderActions(WorkplaceSearchHeaderActions, element, this.data.externalUrl) + renderHeaderActions(WorkplaceSearchHeaderActions, element) ); - return renderApp(WorkplaceSearch, params, coreStart, plugins, this.config, this.data); + return renderApp(WorkplaceSearch, kibanaDeps, pluginData); }, }); @@ -149,23 +152,35 @@ export class EnterpriseSearchPlugin implements Plugin { } } - public start(core: CoreStart) {} + public start() {} public stop() {} + private async getKibanaDeps(core: CoreSetup, params: AppMountParameters) { + // Helper for using start dependencies on mount (instead of setup dependencies) + // and for grouping Kibana-related args together (vs. plugin-specific args) + const [coreStart, pluginsStart] = await core.getStartServices(); + return { params, core: coreStart, plugins: pluginsStart as PluginsStart }; + } + + private getPluginData() { + // Small helper for grouping plugin data related args together + return { config: this.config, data: this.data }; + } + private async getInitialData(http: HttpSetup) { if (!this.config.host) return; // No API to call if (this.hasInitialized) return; // We've already made an initial call try { - const { publicUrl, ...initialData } = await http.get('/api/enterprise_search/config_data'); - this.data = { ...this.data, ...initialData }; - if (publicUrl) this.data.externalUrl = new ExternalUrl(publicUrl); - + this.data = await http.get('/api/enterprise_search/config_data'); this.hasInitialized = true; + + // TODO: This is a temporary workaround to keep the WorkplaceSearchHeaderActions working. + // We'll solve this shortly by ensuring the main app store loads before the header actions. + externalUrl.enterpriseSearchUrl = this.data.publicUrl || this.config.host; } catch { this.data.errorConnecting = true; - // The plugin will attempt to re-fetch config data on page change } } } diff --git a/x-pack/plugins/file_upload/public/util/indexing_service.js b/x-pack/plugins/file_upload/public/util/indexing_service.js index eb22b0228b48a..28cdb602455b5 100644 --- a/x-pack/plugins/file_upload/public/util/indexing_service.js +++ b/x-pack/plugins/file_upload/public/util/indexing_service.js @@ -189,19 +189,16 @@ async function chunkDataAndWriteToIndex({ id, index, data, mappings, settings }) } export async function createIndexPattern(indexPatternName) { - const indexPatterns = await indexPatternService.get(); try { - Object.assign(indexPatterns, { - id: '', - title: indexPatternName, - }); - - await indexPatterns.create(true); - const id = await getIndexPatternId(indexPatternName); - const indexPattern = await indexPatternService.get(id); + const indexPattern = await indexPatternService.createAndSave( + { + title: indexPatternName, + }, + true + ); return { success: true, - id, + id: indexPattern.id, fields: indexPattern.fields, }; } catch (error) { @@ -212,18 +209,6 @@ export async function createIndexPattern(indexPatternName) { } } -async function getIndexPatternId(name) { - const savedObjectSearch = await savedObjectsClient.find({ type: 'index-pattern', perPage: 1000 }); - const indexPatternSavedObjects = savedObjectSearch.savedObjects; - - if (indexPatternSavedObjects) { - const ip = indexPatternSavedObjects.find((i) => i.attributes.title === name); - return ip !== undefined ? ip.id : undefined; - } else { - return undefined; - } -} - export const getExistingIndexNames = async () => { const indexes = await httpService({ url: `/api/index_management/indices`, diff --git a/x-pack/plugins/global_search/server/services/context.mock.ts b/x-pack/plugins/global_search/server/services/context.mock.ts index 7c72686529c15..325d6ea5c6d19 100644 --- a/x-pack/plugins/global_search/server/services/context.mock.ts +++ b/x-pack/plugins/global_search/server/services/context.mock.ts @@ -4,14 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ +import { of } from 'rxjs'; +import { Capabilities } from 'src/core/server'; import { savedObjectsTypeRegistryMock, savedObjectsClientMock, elasticsearchServiceMock, uiSettingsServiceMock, + capabilitiesServiceMock, } from '../../../../../src/core/server/mocks'; -const createContextMock = () => { +const createContextMock = (capabilities: Partial = {}) => { return { core: { savedObjects: { @@ -26,6 +29,10 @@ const createContextMock = () => { uiSettings: { client: uiSettingsServiceMock.createClient(), }, + capabilities: of({ + ...capabilitiesServiceMock.createCapabilities(), + ...capabilities, + } as Capabilities), }, }; }; diff --git a/x-pack/plugins/global_search/server/services/context.test.ts b/x-pack/plugins/global_search/server/services/context.test.ts index 397a1ea170349..640f4c11b363f 100644 --- a/x-pack/plugins/global_search/server/services/context.test.ts +++ b/x-pack/plugins/global_search/server/services/context.test.ts @@ -27,11 +27,15 @@ describe('getContextFactory', () => { expect(coreStart.uiSettings.asScopedToClient).toHaveBeenCalledTimes(1); expect(coreStart.uiSettings.asScopedToClient).toHaveBeenCalledWith(soClient); + expect(coreStart.capabilities.resolveCapabilities).toHaveBeenCalledTimes(1); + expect(coreStart.capabilities.resolveCapabilities).toHaveBeenCalledWith(request); + expect(context).toEqual({ core: { savedObjects: expect.any(Object), elasticsearch: expect.any(Object), uiSettings: expect.any(Object), + capabilities: expect.any(Object), }, }); }); diff --git a/x-pack/plugins/global_search/server/services/context.ts b/x-pack/plugins/global_search/server/services/context.ts index b15deccaae018..62fddcfb152b3 100644 --- a/x-pack/plugins/global_search/server/services/context.ts +++ b/x-pack/plugins/global_search/server/services/context.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { from } from 'rxjs'; import { CoreStart, KibanaRequest } from 'src/core/server'; import { GlobalSearchProviderContext } from '../types'; @@ -30,6 +31,7 @@ export const getContextFactory = (coreStart: CoreStart) => ( uiSettings: { client: coreStart.uiSettings.asScopedToClient(soClient), }, + capabilities: from(coreStart.capabilities.resolveCapabilities(request)), }, }; }; diff --git a/x-pack/plugins/global_search/server/types.ts b/x-pack/plugins/global_search/server/types.ts index 7d3f5ebc5d079..07d21f54d7bf5 100644 --- a/x-pack/plugins/global_search/server/types.ts +++ b/x-pack/plugins/global_search/server/types.ts @@ -10,6 +10,7 @@ import { ILegacyScopedClusterClient, IUiSettingsClient, SavedObjectsClientContract, + Capabilities, } from 'src/core/server'; import { GlobalSearchBatchedResults, @@ -52,6 +53,7 @@ export interface GlobalSearchProviderContext { uiSettings: { client: IUiSettingsClient; }; + capabilities: Observable; }; } diff --git a/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.test.ts b/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.test.ts index 0085331c5be5f..8798fe6694c96 100644 --- a/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.test.ts +++ b/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.test.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsFindResult, SavedObjectsType, SavedObjectTypeRegistry } from 'src/core/server'; +import { + SavedObjectsFindResult, + SavedObjectsType, + SavedObjectTypeRegistry, + Capabilities, +} from 'src/core/server'; import { mapToResult, mapToResults } from './map_object_to_result'; const createType = (props: Partial): SavedObjectsType => { @@ -111,18 +116,17 @@ describe('mapToResult', () => { describe('mapToResults', () => { let typeRegistry: SavedObjectTypeRegistry; + let capabilities: Capabilities; beforeEach(() => { typeRegistry = new SavedObjectTypeRegistry(); - }); - it('converts savedObjects to results', () => { typeRegistry.registerType( createType({ name: 'typeA', management: { defaultSearchField: 'title', - getInAppUrl: (obj) => ({ path: `/type-a/${obj.id}`, uiCapabilitiesPath: '' }), + getInAppUrl: (obj) => ({ path: `/type-a/${obj.id}`, uiCapabilitiesPath: 'test.typeA' }), }, }) ); @@ -131,7 +135,7 @@ describe('mapToResults', () => { name: 'typeB', management: { defaultSearchField: 'description', - getInAppUrl: (obj) => ({ path: `/type-b/${obj.id}`, uiCapabilitiesPath: 'foo' }), + getInAppUrl: (obj) => ({ path: `/type-b/${obj.id}`, uiCapabilitiesPath: 'test.typeB' }), }, }) ); @@ -140,11 +144,37 @@ describe('mapToResults', () => { name: 'typeC', management: { defaultSearchField: 'excerpt', - getInAppUrl: (obj) => ({ path: `/type-c/${obj.id}`, uiCapabilitiesPath: 'bar' }), + getInAppUrl: (obj) => ({ path: `/type-c/${obj.id}`, uiCapabilitiesPath: 'test.typeC' }), }, }) ); + typeRegistry.registerType( + createType({ + name: 'inaccessibleType', + management: { + defaultSearchField: 'excerpt', + getInAppUrl: (obj) => ({ + path: `/inaccessible-type/${obj.id}`, + uiCapabilitiesPath: 'test.inaccessibleType', + }), + }, + }) + ); + + capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, + test: { + typeA: true, + typeB: true, + typeC: true, + inacessibleType: false, + }, + }; + }); + it('converts savedObjects to results', () => { const results = [ createObject( { @@ -181,7 +211,7 @@ describe('mapToResults', () => { ), ]; - expect(mapToResults(results, typeRegistry)).toEqual([ + expect(mapToResults(results, typeRegistry, capabilities)).toEqual([ { id: 'resultA', title: 'titleA', @@ -205,4 +235,41 @@ describe('mapToResults', () => { }, ]); }); + + it('filters results without permissions', () => { + const results = [ + createObject( + { + id: 'resultA', + type: 'typeA', + score: 100, + }, + { + title: 'titleA', + field: 'noise', + } + ), + createObject( + { + id: 'inaccessibleResult', + type: 'inaccessibleType', + score: 92, + }, + { + excerpt: 'inaccessibleTitle', + title: 'inaccessible', + } + ), + ]; + + expect(mapToResults(results, typeRegistry, capabilities)).toEqual([ + { + id: 'resultA', + title: 'titleA', + type: 'typeA', + url: '/type-a/resultA', + score: 100, + }, + ]); + }); }); diff --git a/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.ts b/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.ts index c93558b1a3cf4..14641e1aaffff 100644 --- a/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.ts +++ b/x-pack/plugins/global_search_providers/server/providers/saved_objects/map_object_to_result.ts @@ -4,18 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ +import get from 'lodash/get'; import { SavedObjectsType, ISavedObjectTypeRegistry, SavedObjectsFindResult, + Capabilities, } from 'src/core/server'; import { GlobalSearchProviderResult } from '../../../../global_search/server'; export const mapToResults = ( objects: Array>, - registry: ISavedObjectTypeRegistry + registry: ISavedObjectTypeRegistry, + capabilities: Capabilities ): GlobalSearchProviderResult[] => { - return objects.map((obj) => mapToResult(obj, registry.getType(obj.type)!)); + return objects + .filter((obj) => isAccessible(obj, registry.getType(obj.type)!, capabilities)) + .map((obj) => mapToResult(obj, registry.getType(obj.type)!)); +}; + +const isAccessible = ( + object: SavedObjectsFindResult, + type: SavedObjectsType, + capabilities: Capabilities +): boolean => { + const { getInAppUrl } = type.management ?? {}; + if (getInAppUrl === undefined) { + throw new Error('Trying to map an object from a type without management metadata'); + } + const { uiCapabilitiesPath } = getInAppUrl(object); + return Boolean(get(capabilities, uiCapabilitiesPath) ?? false); }; export const mapToResult = ( diff --git a/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.test.ts b/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.test.ts index 11e3a40ddee17..352191658ed0d 100644 --- a/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.test.ts +++ b/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.test.ts @@ -33,16 +33,20 @@ const createFindResponse = ( total: results.length, }); -const createType = (props: Partial): SavedObjectsType => { +const createType = ( + props: Pick & Partial +): SavedObjectsType => { return { - name: 'type', hidden: false, namespaceType: 'single', mappings: { properties: {} }, ...props, management: { defaultSearchField: 'field', - getInAppUrl: (obj) => ({ path: `/object/${obj.id}`, uiCapabilitiesPath: '' }), + getInAppUrl: (obj) => ({ + path: `/object/${obj.id}`, + uiCapabilitiesPath: `types.${props.name}`, + }), ...props.management, }, }; @@ -82,7 +86,7 @@ describe('savedObjectsResultProvider', () => { name: 'typeA', management: { defaultSearchField: 'title', - getInAppUrl: (obj) => ({ path: `/type-a/${obj.id}`, uiCapabilitiesPath: '' }), + getInAppUrl: (obj) => ({ path: `/type-a/${obj.id}`, uiCapabilitiesPath: 'test.typeA' }), }, }) ); @@ -91,12 +95,17 @@ describe('savedObjectsResultProvider', () => { name: 'typeB', management: { defaultSearchField: 'description', - getInAppUrl: (obj) => ({ path: `/type-b/${obj.id}`, uiCapabilitiesPath: 'foo' }), + getInAppUrl: (obj) => ({ path: `/type-b/${obj.id}`, uiCapabilitiesPath: 'test.typeB' }), }, }) ); - context = globalSearchPluginMock.createProviderContext(); + context = globalSearchPluginMock.createProviderContext({ + test: { + typeA: true, + typeB: true, + }, + }); context.core.savedObjects.client.find.mockResolvedValue(createFindResponse([])); context.core.savedObjects.typeRegistry = registry as any; }); diff --git a/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.ts b/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.ts index 8a3d3d732531f..1c79380fe17fd 100644 --- a/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.ts +++ b/x-pack/plugins/global_search_providers/server/providers/saved_objects/provider.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { from } from 'rxjs'; -import { map, takeUntil } from 'rxjs/operators'; +import { from, combineLatest } from 'rxjs'; +import { map, takeUntil, first } from 'rxjs/operators'; import { GlobalSearchResultProvider } from '../../../../global_search/server'; import { mapToResults } from './map_object_to_result'; @@ -13,7 +13,10 @@ export const createSavedObjectsResultProvider = (): GlobalSearchResultProvider = return { id: 'savedObjects', find: (term, { aborted$, maxResults, preference }, { core }) => { - const { typeRegistry, client } = core.savedObjects; + const { + capabilities, + savedObjects: { client, typeRegistry }, + } = core; const searchableTypes = typeRegistry .getVisibleTypes() @@ -31,9 +34,9 @@ export const createSavedObjectsResultProvider = (): GlobalSearchResultProvider = type: searchableTypes.map((type) => type.name), }); - return from(responsePromise).pipe( + return combineLatest([from(responsePromise), capabilities.pipe(first())]).pipe( takeUntil(aborted$), - map((res) => mapToResults(res.saved_objects, typeRegistry)) + map(([res, cap]) => mapToResults(res.saved_objects, typeRegistry, cap)) ); }, }; diff --git a/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js b/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js index 75569f472e219..b523dc3c1ba6c 100644 --- a/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js +++ b/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js @@ -5,6 +5,8 @@ */ import React from 'react'; import { i18n } from '@kbn/i18n'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isEmpty from 'lodash/isEmpty'; import './brace_imports'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts index 753ffb111cf13..222887069a0dc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts @@ -3,6 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import cloneDeep from 'lodash/cloneDeep'; import { serializePolicy } from './policy_serialization'; import { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/shared/serialize_phase_with_allocation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/shared/serialize_phase_with_allocation.ts index 5a9db3069aea6..c29ae3ab22831 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/shared/serialize_phase_with_allocation.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/shared/serialize_phase_with_allocation.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import cloneDeep from 'lodash/cloneDeep'; import { diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 6fbe177d24e06..22e6f09907d75 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -8,7 +8,7 @@ import React, { createContext, useContext } from 'react'; import { ScopedHistory } from 'kibana/public'; import { ManagementAppMountParams } from 'src/plugins/management/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; -import { CoreStart } from '../../../../../src/core/public'; +import { CoreSetup, CoreStart } from '../../../../../src/core/public'; import { IngestManagerSetup } from '../../../ingest_manager/public'; import { IndexMgmtMetricsType } from '../types'; @@ -34,6 +34,7 @@ export interface AppDependencies { }; history: ScopedHistory; setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; + uiSettings: CoreSetup['uiSettings']; } export const AppContextProvider = ({ diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts index b3bf071948956..c47ea4a884111 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts @@ -73,6 +73,10 @@ export * from './meta_parameter'; export * from './ignore_above_parameter'; +export { RuntimeTypeParameter } from './runtime_type_parameter'; + +export { PainlessScriptParameter } from './painless_script_parameter'; + export const PARAMETER_SERIALIZERS = [relationsSerializer, dynamicSerializer]; export const PARAMETER_DESERIALIZERS = [relationsDeserializer, dynamicDeserializer]; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/painless_script_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/painless_script_parameter.tsx new file mode 100644 index 0000000000000..19746034b530c --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/painless_script_parameter.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFormRow, EuiDescribedFormGroup } from '@elastic/eui'; + +import { CodeEditor, UseField } from '../../../shared_imports'; +import { getFieldConfig } from '../../../lib'; +import { EditFieldFormRow } from '../fields/edit_field'; + +interface Props { + stack?: boolean; +} + +export const PainlessScriptParameter = ({ stack }: Props) => { + return ( + + {(scriptField) => { + const error = scriptField.getErrorsMessages(); + const isInvalid = error ? Boolean(error.length) : false; + + const field = ( + + + + ); + + const fieldTitle = i18n.translate('xpack.idxMgmt.mappingsEditor.painlessScript.title', { + defaultMessage: 'Emitted value', + }); + + const fieldDescription = i18n.translate( + 'xpack.idxMgmt.mappingsEditor.painlessScript.description', + { + defaultMessage: 'Use emit() to define the value of this runtime field.', + } + ); + + if (stack) { + return ( + + {field} + + ); + } + + return ( + {fieldTitle}} + description={fieldDescription} + fullWidth={true} + > + {field} + + ); + }} + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx index 6575fe1fac7b8..62810df44b5af 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx @@ -93,17 +93,17 @@ export const PathParameter = ({ field, allFields }: Props) => { <> {!Boolean(suggestedFields.length) && ( <> - -

- {i18n.translate( - 'xpack.idxMgmt.mappingsEditor.aliasType.noFieldsAddedWarningMessage', - { - defaultMessage: - 'You need to add at least one field before creating an alias.', - } - )} -

-
+ )} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/runtime_type_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/runtime_type_parameter.tsx new file mode 100644 index 0000000000000..4bdb15af5e7d9 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/runtime_type_parameter.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFormRow, + EuiComboBox, + EuiComboBoxOptionOption, + EuiDescribedFormGroup, + EuiSpacer, +} from '@elastic/eui'; + +import { UseField } from '../../../shared_imports'; +import { DataType } from '../../../types'; +import { getFieldConfig } from '../../../lib'; +import { RUNTIME_FIELD_OPTIONS, TYPE_DEFINITION } from '../../../constants'; +import { EditFieldFormRow, FieldDescriptionSection } from '../fields/edit_field'; + +interface Props { + stack?: boolean; +} + +export const RuntimeTypeParameter = ({ stack }: Props) => { + return ( + + {(runtimeTypeField) => { + const { label, value, setValue } = runtimeTypeField; + const typeDefinition = + TYPE_DEFINITION[(value as EuiComboBoxOptionOption[])[0]!.value as DataType]; + + const field = ( + <> + + + + + + + {/* Field description */} + {typeDefinition && ( + + {typeDefinition.description?.() as JSX.Element} + + )} + + ); + + const fieldTitle = i18n.translate('xpack.idxMgmt.mappingsEditor.runtimeType.title', { + defaultMessage: 'Emitted type', + }); + + const fieldDescription = i18n.translate( + 'xpack.idxMgmt.mappingsEditor.runtimeType.description', + { + defaultMessage: 'Select the type of value emitted by the runtime field.', + } + ); + + if (stack) { + return ( + + {field} + + ); + } + + return ( + {fieldTitle}} + description={fieldDescription} + fullWidth={true} + > + {field} + + ); + }} + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/term_vector_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/term_vector_parameter.tsx index 6752bb6d6af2b..69d56032eed6a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/term_vector_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/term_vector_parameter.tsx @@ -56,14 +56,17 @@ export const TermVectorParameter = ({ field, defaultToggleValue }: Props) => { {formData.term_vector === 'with_positions_offsets' && ( <> - -

- {i18n.translate('xpack.idxMgmt.mappingsEditor.termVectorFieldWarningMessage', { + - + } + )} + /> )} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx index a8170b1d59fbb..cff2b9af4fd10 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx @@ -14,15 +14,17 @@ import { EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector, + EuiSpacer, } from '@elastic/eui'; import { useForm, Form, FormDataProvider } from '../../../../shared_imports'; -import { EUI_SIZE } from '../../../../constants'; +import { EUI_SIZE, TYPE_DEFINITION } from '../../../../constants'; import { useDispatch } from '../../../../mappings_state_context'; import { fieldSerializer } from '../../../../lib'; -import { Field, NormalizedFields } from '../../../../types'; +import { Field, NormalizedFields, MainType } from '../../../../types'; import { NameParameter, TypeParameter, SubTypeParameter } from '../../field_parameters'; -import { getParametersFormForType } from './required_parameters_forms'; +import { FieldBetaBadge } from '../field_beta_badge'; +import { getRequiredParametersFormForType } from './required_parameters_forms'; const formWrapper = (props: any) =>

; @@ -195,18 +197,27 @@ export const CreateField = React.memo(function CreateFieldComponent({ {({ type, subType }) => { - const ParametersForm = getParametersFormForType( + const RequiredParametersForm = getRequiredParametersFormForType( type?.[0].value, subType?.[0].value ); - if (!ParametersForm) { + if (!RequiredParametersForm) { return null; } + const typeDefinition = TYPE_DEFINITION[type?.[0].value as MainType]; + return (
- + {typeDefinition.isBeta ? ( + <> + + + + ) : null} + +
); }} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/required_parameters_forms/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/required_parameters_forms/index.ts index 1112bf99713ed..5c04b2fbb336c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/required_parameters_forms/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/required_parameters_forms/index.ts @@ -11,6 +11,7 @@ import { AliasTypeRequiredParameters } from './alias_type'; import { TokenCountTypeRequiredParameters } from './token_count_type'; import { ScaledFloatTypeRequiredParameters } from './scaled_float_type'; import { DenseVectorRequiredParameters } from './dense_vector_type'; +import { RuntimeTypeRequiredParameters } from './runtime_type'; export interface ComponentProps { allFields: NormalizedFields['byId']; @@ -21,9 +22,10 @@ const typeToParametersFormMap: { [key in DataType]?: ComponentType } = { token_count: TokenCountTypeRequiredParameters, scaled_float: ScaledFloatTypeRequiredParameters, dense_vector: DenseVectorRequiredParameters, + runtime: RuntimeTypeRequiredParameters, }; -export const getParametersFormForType = ( +export const getRequiredParametersFormForType = ( type: MainType, subType?: SubType ): ComponentType | undefined => diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/required_parameters_forms/runtime_type.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/required_parameters_forms/runtime_type.tsx new file mode 100644 index 0000000000000..54907295f8a15 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/required_parameters_forms/runtime_type.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { RuntimeTypeParameter, PainlessScriptParameter } from '../../../field_parameters'; + +export const RuntimeTypeRequiredParameters = () => { + return ( + <> + + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx index 3b55c5ac076c2..e91a666cc4221 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx @@ -5,12 +5,13 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { FormDataProvider } from '../../../../shared_imports'; -import { MainType, SubType, Field } from '../../../../types'; +import { MainType, SubType, Field, DataTypeDefinition } from '../../../../types'; import { TYPE_DEFINITION } from '../../../../constants'; import { NameParameter, TypeParameter, SubTypeParameter } from '../../field_parameters'; +import { FieldBetaBadge } from '../field_beta_badge'; import { FieldDescriptionSection } from './field_description_section'; interface Props { @@ -19,6 +20,25 @@ interface Props { isMultiField: boolean; } +const getTypeDefinition = (type: MainType, subType: SubType): DataTypeDefinition | undefined => { + if (!type) { + return; + } + + const typeDefinition = TYPE_DEFINITION[type]; + const hasSubType = typeDefinition.subTypes !== undefined; + + if (hasSubType) { + if (subType) { + return TYPE_DEFINITION[subType]; + } + + return; + } + + return typeDefinition; +}; + export const EditFieldHeaderForm = React.memo( ({ defaultValue, isRootLevelField, isMultiField }: Props) => { return ( @@ -56,27 +76,29 @@ export const EditFieldHeaderForm = React.memo( {/* Field description */} - - - {({ type, subType }) => { - if (!type) { - return null; - } - const typeDefinition = TYPE_DEFINITION[type[0].value as MainType]; - const hasSubType = typeDefinition.subTypes !== undefined; - - if (hasSubType) { - if (subType) { - const subTypeDefinition = TYPE_DEFINITION[subType as SubType]; - return (subTypeDefinition?.description?.() as JSX.Element) ?? null; - } - return null; - } + + {({ type, subType }) => { + const typeDefinition = getTypeDefinition( + type[0].value as MainType, + subType && (subType[0].value as SubType) + ); + const description = (typeDefinition?.description?.() as JSX.Element) ?? null; - return typeDefinition.description?.() as JSX.Element; - }} - - + return ( + <> + + + {typeDefinition?.isBeta && } + + + + + {description} + + + ); + }} +
); } diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/field_description_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/field_description_section.tsx index 2040d7f40d5cb..2301f7a47bf2f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/field_description_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/field_description_section.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; interface Props { @@ -19,7 +19,6 @@ export const FieldDescriptionSection = ({ children, isMultiField }: Props) => { return (
- {children} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_beta_badge.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_beta_badge.tsx new file mode 100644 index 0000000000000..99c725e8a00b3 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_beta_badge.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiBetaBadge } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const FieldBetaBadge = () => { + const betaText = i18n.translate('xpack.idxMgmt.mappingsEditor.fieldBetaBadgeLabel', { + defaultMessage: 'Beta', + }); + + const tooltipText = i18n.translate('xpack.idxMgmt.mappingsEditor.fieldBetaBadgeTooltip', { + defaultMessage: 'This field type is not GA. Please help us by reporting any bugs.', + }); + + return ; +}; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts index 0f9308aa43448..d135d1b81419c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts @@ -31,6 +31,7 @@ import { JoinType } from './join_type'; import { HistogramType } from './histogram_type'; import { ConstantKeywordType } from './constant_keyword_type'; import { RankFeatureType } from './rank_feature_type'; +import { RuntimeType } from './runtime_type'; import { WildcardType } from './wildcard_type'; import { PointType } from './point_type'; import { VersionType } from './version_type'; @@ -61,6 +62,7 @@ const typeToParametersFormMap: { [key in DataType]?: ComponentType } = { histogram: HistogramType, constant_keyword: ConstantKeywordType, rank_feature: RankFeatureType, + runtime: RuntimeType, wildcard: WildcardType, point: PointType, version: VersionType, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/runtime_type.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/runtime_type.tsx new file mode 100644 index 0000000000000..dcf5a74e0e304 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/runtime_type.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { RuntimeTypeParameter, PainlessScriptParameter } from '../../field_parameters'; +import { BasicParametersSection } from '../edit_field'; + +export const RuntimeType = () => { + return ( + + + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx index 4ab0ea0fb355b..1939f09fa6762 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx @@ -16,7 +16,7 @@ import { import { i18n } from '@kbn/i18n'; import { NormalizedField, NormalizedFields } from '../../../types'; -import { getTypeLabelFromType } from '../../../lib'; +import { getTypeLabelFromField } from '../../../lib'; import { CHILD_FIELD_INDENT_SIZE, LEFT_PADDING_SIZE_FIELD_ITEM_WRAPPER } from '../../../constants'; import { FieldsList } from './fields_list'; @@ -67,6 +67,7 @@ function FieldListItemComponent( isExpanded, path, } = field; + // When there aren't any "child" fields (the maxNestedDepth === 0), there is no toggle icon on the left of any field. // For that reason, we need to compensate and substract some indent to left align on the page. const substractIndentAmount = maxNestedDepth === 0 ? CHILD_FIELD_INDENT_SIZE * 0.5 : 0; @@ -280,10 +281,10 @@ function FieldListItemComponent( ? i18n.translate('xpack.idxMgmt.mappingsEditor.multiFieldBadgeLabel', { defaultMessage: '{dataType} multi-field', values: { - dataType: getTypeLabelFromType(source.type), + dataType: getTypeLabelFromField(source), }, }) - : getTypeLabelFromType(source.type)} + : getTypeLabelFromField(source)} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx index a2d9a50f28394..a4cc4b4776e2b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { SearchResult } from '../../../types'; import { TYPE_DEFINITION } from '../../../constants'; import { useDispatch } from '../../../mappings_state_context'; -import { getTypeLabelFromType } from '../../../lib'; +import { getTypeLabelFromField } from '../../../lib'; import { DeleteFieldProvider } from '../fields/delete_field_provider'; interface Props { @@ -121,7 +121,7 @@ export const SearchResultItem = React.memo(function FieldListItemFlatComponent({ dataType: TYPE_DEFINITION[source.type].label, }, }) - : getTypeLabelFromType(source.type)} + : getTypeLabelFromField(source)} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx index 66be208fbb66b..07ca0a69afefb 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx @@ -13,6 +13,25 @@ import { documentationService } from '../../../services/documentation'; import { MainType, SubType, DataType, DataTypeDefinition } from '../types'; export const TYPE_DEFINITION: { [key in DataType]: DataTypeDefinition } = { + runtime: { + value: 'runtime', + isBeta: true, + label: i18n.translate('xpack.idxMgmt.mappingsEditor.dataType.runtimeFieldDescription', { + defaultMessage: 'Runtime', + }), + // TODO: Add this once the page exists. + // documentation: { + // main: '/runtime_field.html', + // }, + description: () => ( +

+ +

+ ), + }, text: { value: 'text', label: i18n.translate('xpack.idxMgmt.mappingsEditor.dataType.textDescription', { @@ -925,6 +944,7 @@ export const MAIN_TYPES: MainType[] = [ 'range', 'rank_feature', 'rank_features', + 'runtime', 'search_as_you_type', 'shape', 'text', diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/field_options.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/field_options.tsx index d16bf68b80e5d..25fdac5089b86 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/field_options.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/field_options.tsx @@ -18,6 +18,7 @@ export const TYPE_NOT_ALLOWED_MULTIFIELD: DataType[] = [ 'object', 'nested', 'alias', + 'runtime', ]; export const FIELD_TYPES_OPTIONS = Object.entries(MAIN_DATA_TYPE_DEFINITION).map( @@ -27,6 +28,35 @@ export const FIELD_TYPES_OPTIONS = Object.entries(MAIN_DATA_TYPE_DEFINITION).map }) ) as ComboBoxOption[]; +export const RUNTIME_FIELD_OPTIONS = [ + { + label: 'Keyword', + value: 'keyword', + }, + { + label: 'Long', + value: 'long', + }, + { + label: 'Double', + value: 'double', + }, + { + label: 'Date', + value: 'date', + }, + { + label: 'IP', + value: 'ip', + }, + { + label: 'Boolean', + value: 'boolean', + }, +] as ComboBoxOption[]; + +export const RUNTIME_FIELD_TYPES = ['keyword', 'long', 'double', 'date', 'ip', 'boolean'] as const; + interface SuperSelectOptionConfig { inputDisplay: string; dropdownDisplay: JSX.Element; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx index 4ffedc8ca114d..4c9786d88bfa2 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx @@ -20,6 +20,7 @@ import { import { AliasOption, DataType, + RuntimeType, ComboBoxOption, ParameterName, ParameterDefinition, @@ -27,6 +28,7 @@ import { import { documentationService } from '../../../services/documentation'; import { INDEX_DEFAULT } from './default_values'; import { TYPE_DEFINITION } from './data_types_definition'; +import { RUNTIME_FIELD_OPTIONS } from './field_options'; const { toInt } = fieldFormatters; const { emptyField, containsCharsField, numberGreaterThanField, isJsonField } = fieldValidators; @@ -185,6 +187,52 @@ export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinitio }, schema: t.string, }, + runtime_type: { + fieldConfig: { + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate('xpack.idxMgmt.mappingsEditor.parameters.runtimeTypeLabel', { + defaultMessage: 'Type', + }), + defaultValue: 'keyword', + deserializer: (fieldType: RuntimeType | undefined) => { + if (typeof fieldType === 'string' && Boolean(fieldType)) { + const label = + RUNTIME_FIELD_OPTIONS.find(({ value }) => value === fieldType)?.label ?? fieldType; + return [ + { + label, + value: fieldType, + }, + ]; + } + return []; + }, + serializer: (value: ComboBoxOption[]) => (value.length === 0 ? '' : value[0].value), + }, + schema: t.string, + }, + script: { + fieldConfig: { + defaultValue: '', + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.idxMgmt.mappingsEditor.parameters.painlessScriptLabel', { + defaultMessage: 'Script', + }), + validations: [ + { + validator: emptyField( + i18n.translate( + 'xpack.idxMgmt.mappingsEditor.parameters.validations.scriptIsRequiredErrorMessage', + { + defaultMessage: 'Script must emit() a value.', + } + ) + ), + }, + ], + }, + schema: t.string, + }, store: { fieldConfig: { type: FIELD_TYPES.CHECKBOX, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/index.ts index 8cd1bbf0764ab..0a59cafdcef47 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/index.ts @@ -4,7 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './utils'; +export { + getUniqueId, + getChildFieldsName, + getFieldMeta, + getTypeLabelFromField, + getFieldConfig, + getTypeMetaFromSource, + normalize, + deNormalize, + updateFieldsPathAfterFieldNameChange, + getAllChildFields, + getAllDescendantAliases, + getFieldAncestors, + filterTypesForMultiField, + filterTypesForNonRootFields, + getMaxNestedDepth, + buildFieldTreeFromIds, + shouldDeleteChildFieldsAfterTypeChange, + canUseMappingsEditor, + stripUndefinedValues, +} from './utils'; export * from './serializers'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts index bc495b05e07b7..e1988c071314e 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../constants', () => ({ MAIN_DATA_TYPE_DEFINITION: {} })); +jest.mock('../constants', () => { + const { TYPE_DEFINITION } = jest.requireActual('../constants'); + return { MAIN_DATA_TYPE_DEFINITION: {}, TYPE_DEFINITION }; +}); -import { stripUndefinedValues } from './utils'; +import { stripUndefinedValues, getTypeLabelFromField } from './utils'; describe('utils', () => { describe('stripUndefinedValues()', () => { @@ -53,4 +56,46 @@ describe('utils', () => { expect(stripUndefinedValues(dataIN)).toEqual(dataOUT); }); }); + + describe('getTypeLabelFromField()', () => { + test('returns an unprocessed label for non-runtime fields', () => { + expect( + getTypeLabelFromField({ + name: 'testField', + type: 'keyword', + }) + ).toBe('Keyword'); + }); + + test(`returns a label prepended with 'Other' for unrecognized fields`, () => { + expect( + getTypeLabelFromField({ + name: 'testField', + // @ts-ignore + type: 'hyperdrive', + }) + ).toBe('Other: hyperdrive'); + }); + + test("returns a label prepended with 'Runtime' for runtime fields", () => { + expect( + getTypeLabelFromField({ + name: 'testField', + type: 'runtime', + runtime_type: 'keyword', + }) + ).toBe('Runtime Keyword'); + }); + + test("returns a label prepended with 'Runtime Other' for unrecognized runtime fields", () => { + expect( + getTypeLabelFromField({ + name: 'testField', + type: 'runtime', + // @ts-ignore + runtime_type: 'hyperdrive', + }) + ).toBe('Runtime Other: hyperdrive'); + }); + }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts index 8b3ff60005305..d96f20683216a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts @@ -71,8 +71,23 @@ export const getFieldMeta = (field: Field, isMultiField?: boolean): FieldMeta => }; }; -export const getTypeLabelFromType = (type: DataType) => - TYPE_DEFINITION[type] ? TYPE_DEFINITION[type].label : `${TYPE_DEFINITION.other.label}: ${type}`; +const getTypeLabel = (type?: DataType): string => { + return type && TYPE_DEFINITION[type] + ? TYPE_DEFINITION[type].label + : `${TYPE_DEFINITION.other.label}: ${type}`; +}; + +export const getTypeLabelFromField = (field: Field) => { + const { type, runtime_type: runtimeType } = field; + const typeLabel = getTypeLabel(type); + + if (type === 'runtime') { + const runtimeTypeLabel = getTypeLabel(runtimeType); + return `${typeLabel} ${runtimeTypeLabel}`; + } + + return typeLabel; +}; export const getFieldConfig = ( param: ParameterName, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts index 097d039527950..54b2486108183 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts @@ -51,3 +51,5 @@ export { OnJsonEditorUpdateHandler, GlobalFlyout, } from '../../../../../../../src/plugins/es_ui_shared/public'; + +export { CodeEditor } from '../../../../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts index 435c2ddd37f6b..926b4c9d12bee 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts @@ -8,7 +8,7 @@ import { ReactNode } from 'react'; import { GenericObject } from './mappings_editor'; import { FieldConfig } from '../shared_imports'; -import { PARAMETERS_DEFINITION } from '../constants'; +import { PARAMETERS_DEFINITION, RUNTIME_FIELD_TYPES } from '../constants'; export interface DataTypeDefinition { label: string; @@ -19,6 +19,7 @@ export interface DataTypeDefinition { }; subTypes?: { label: string; types: SubType[] }; description?: () => ReactNode; + isBeta?: boolean; } export interface ParameterDefinition { @@ -35,6 +36,7 @@ export interface ParameterDefinition { } export type MainType = + | 'runtime' | 'text' | 'keyword' | 'numeric' @@ -74,6 +76,8 @@ export type SubType = NumericType | RangeType; export type DataType = MainType | SubType; +export type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; + export type NumericType = | 'long' | 'integer' @@ -152,6 +156,8 @@ export type ParameterName = | 'depth_limit' | 'relations' | 'max_shingle_size' + | 'runtime_type' + | 'script' | 'value' | 'meta'; @@ -169,6 +175,7 @@ export interface Fields { interface FieldBasic { name: string; type: DataType; + runtime_type?: DataType; subType?: SubType; properties?: { [key: string]: Omit }; fields?: { [key: string]: Omit }; diff --git a/x-pack/plugins/index_management/public/application/index.tsx b/x-pack/plugins/index_management/public/application/index.tsx index f881c2e01cefc..d8b5da8361c43 100644 --- a/x-pack/plugins/index_management/public/application/index.tsx +++ b/x-pack/plugins/index_management/public/application/index.tsx @@ -11,7 +11,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { CoreStart } from '../../../../../src/core/public'; import { API_BASE_PATH } from '../../common'; -import { GlobalFlyout } from '../shared_imports'; +import { createKibanaReactContext, GlobalFlyout } from '../shared_imports'; import { AppContextProvider, AppDependencies } from './app_context'; import { App } from './app'; @@ -30,7 +30,12 @@ export const renderApp = ( const { i18n, docLinks, notifications, application } = core; const { Context: I18nContext } = i18n; - const { services, history, setBreadcrumbs } = dependencies; + const { services, history, setBreadcrumbs, uiSettings } = dependencies; + + // uiSettings is required by the CodeEditor component used to edit runtime field Painless scripts. + const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ + uiSettings, + }); const componentTemplateProviderValues = { httpClient: services.httpService.httpClient, @@ -44,17 +49,19 @@ export const renderApp = ( render( - - - - - - - - - - - + + + + + + + + + + + + + , elem ); diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index 6257b68430cf0..f7b728c875762 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -41,6 +41,7 @@ export async function mountManagementSection( fatalErrors, application, chrome: { docTitle }, + uiSettings, } = core; docTitle.change(PLUGIN.getI18nName(i18n)); @@ -60,6 +61,7 @@ export async function mountManagementSection( services, history, setBreadcrumbs, + uiSettings, }; const unmountAppCallback = renderApp(element, { core, dependencies: appDependencies }); diff --git a/x-pack/plugins/index_management/public/shared_imports.ts b/x-pack/plugins/index_management/public/shared_imports.ts index d58545768732e..acb3eb512e2c1 100644 --- a/x-pack/plugins/index_management/public/shared_imports.ts +++ b/x-pack/plugins/index_management/public/shared_imports.ts @@ -44,4 +44,7 @@ export { export { isJSON } from '../../../../src/plugins/es_ui_shared/static/validators/string'; -export { reactRouterNavigate } from '../../../../src/plugins/kibana_react/public'; +export { + createKibanaReactContext, + reactRouterNavigate, +} from '../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/index_management/server/plugin.ts b/x-pack/plugins/index_management/server/plugin.ts index 30aeeb6b45362..ae9633f3e22b9 100644 --- a/x-pack/plugins/index_management/server/plugin.ts +++ b/x-pack/plugins/index_management/server/plugin.ts @@ -84,7 +84,9 @@ export class IndexMgmtServerPlugin implements Plugin; + +export const anomalyTypeRT = rt.keyof({ + metrics_hosts: null, + metrics_k8s: null, +}); + +export type AnomalyType = rt.TypeOf; + +const sortOptionsRT = rt.keyof({ + anomalyScore: null, + dataset: null, + startTime: null, +}); + +const sortDirectionsRT = rt.keyof({ + asc: null, + desc: null, +}); + +const paginationPreviousPageCursorRT = rt.type({ + searchBefore: paginationCursorRT, +}); + +const paginationNextPageCursorRT = rt.type({ + searchAfter: paginationCursorRT, +}); + +export const paginationRT = rt.intersection([ + rt.type({ + pageSize: rt.number, + }), + rt.partial({ + cursor: rt.union([paginationPreviousPageCursorRT, paginationNextPageCursorRT]), + }), +]); + +export type Pagination = rt.TypeOf; + +export const sortRT = rt.type({ + field: sortOptionsRT, + direction: sortDirectionsRT, +}); + +export type Sort = rt.TypeOf; diff --git a/x-pack/plugins/security_solution/server/graphql/ip_details/index.ts b/x-pack/plugins/infra/common/http_api/infra_ml/results/index.ts similarity index 68% rename from x-pack/plugins/security_solution/server/graphql/ip_details/index.ts rename to x-pack/plugins/infra/common/http_api/infra_ml/results/index.ts index 186397ea347cb..efd597a043e07 100644 --- a/x-pack/plugins/security_solution/server/graphql/ip_details/index.ts +++ b/x-pack/plugins/infra/common/http_api/infra_ml/results/index.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createIpDetailsResolvers } from './resolvers'; -export { ipDetailsSchemas } from './schema.gql'; +export * from './metrics_hosts_anomalies'; +export * from './metrics_k8s_anomalies'; +export * from './common'; diff --git a/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_hosts_anomalies.ts new file mode 100644 index 0000000000000..9fdac09fec20e --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_hosts_anomalies.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { timeRangeRT, routeTimingMetadataRT } from '../../shared'; +import { anomalyTypeRT, paginationCursorRT, sortRT, paginationRT } from './common'; + +export const INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH = + '/api/infra/infra_ml/results/metrics_hosts_anomalies'; + +const metricsHostAnomalyCommonFieldsRT = rt.type({ + id: rt.string, + anomalyScore: rt.number, + typical: rt.number, + actual: rt.number, + type: anomalyTypeRT, + duration: rt.number, + startTime: rt.number, + jobId: rt.string, +}); +const metricsHostsAnomalyRT = metricsHostAnomalyCommonFieldsRT; + +export type MetricsHostsAnomaly = rt.TypeOf; + +export const getMetricsHostsAnomaliesSuccessReponsePayloadRT = rt.intersection([ + rt.type({ + data: rt.intersection([ + rt.type({ + anomalies: rt.array(metricsHostsAnomalyRT), + // Signifies there are more entries backwards or forwards. If this was a request + // for a previous page, there are more previous pages, if this was a request for a next page, + // there are more next pages. + hasMoreEntries: rt.boolean, + }), + rt.partial({ + paginationCursors: rt.type({ + // The cursor to use to fetch the previous page + previousPageCursor: paginationCursorRT, + // The cursor to use to fetch the next page + nextPageCursor: paginationCursorRT, + }), + }), + ]), + }), + rt.partial({ + timing: routeTimingMetadataRT, + }), +]); + +export type GetMetricsHostsAnomaliesSuccessResponsePayload = rt.TypeOf< + typeof getMetricsHostsAnomaliesSuccessReponsePayloadRT +>; + +export const getMetricsHostsAnomaliesRequestPayloadRT = rt.type({ + data: rt.intersection([ + rt.type({ + // the ID of the source configuration + sourceId: rt.string, + // the time range to fetch the log entry anomalies from + timeRange: timeRangeRT, + }), + rt.partial({ + // Pagination properties + pagination: paginationRT, + // Sort properties + sort: sortRT, + // // Dataset filters + // datasets: rt.array(rt.string), + }), + ]), +}); + +export type GetMetricsHostsAnomaliesRequestPayload = rt.TypeOf< + typeof getMetricsHostsAnomaliesRequestPayloadRT +>; diff --git a/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_k8s_anomalies.ts new file mode 100644 index 0000000000000..ab1f245a74c0c --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_k8s_anomalies.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { timeRangeRT, routeTimingMetadataRT } from '../../shared'; +import { paginationCursorRT, anomalyTypeRT, sortRT, paginationRT } from './common'; + +export const INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH = + '/api/infra/infra_ml/results/metrics_k8s_anomalies'; + +const metricsK8sAnomalyCommonFieldsRT = rt.type({ + id: rt.string, + anomalyScore: rt.number, + typical: rt.number, + actual: rt.number, + type: anomalyTypeRT, + duration: rt.number, + startTime: rt.number, + jobId: rt.string, +}); +const metricsK8sAnomalyRT = metricsK8sAnomalyCommonFieldsRT; + +export type MetricsK8sAnomaly = rt.TypeOf; + +export const getMetricsK8sAnomaliesSuccessReponsePayloadRT = rt.intersection([ + rt.type({ + data: rt.intersection([ + rt.type({ + anomalies: rt.array(metricsK8sAnomalyRT), + // Signifies there are more entries backwards or forwards. If this was a request + // for a previous page, there are more previous pages, if this was a request for a next page, + // there are more next pages. + hasMoreEntries: rt.boolean, + }), + rt.partial({ + paginationCursors: rt.type({ + // The cursor to use to fetch the previous page + previousPageCursor: paginationCursorRT, + // The cursor to use to fetch the next page + nextPageCursor: paginationCursorRT, + }), + }), + ]), + }), + rt.partial({ + timing: routeTimingMetadataRT, + }), +]); + +export type GetMetricsK8sAnomaliesSuccessResponsePayload = rt.TypeOf< + typeof getMetricsK8sAnomaliesSuccessReponsePayloadRT +>; + +export const getMetricsK8sAnomaliesRequestPayloadRT = rt.type({ + data: rt.intersection([ + rt.type({ + // the ID of the source configuration + sourceId: rt.string, + // the time range to fetch the log entry anomalies from + timeRange: timeRangeRT, + }), + rt.partial({ + // Pagination properties + pagination: paginationRT, + // Sort properties + sort: sortRT, + // Dataset filters + datasets: rt.array(rt.string), + }), + ]), +}); + +export type GetMetricsK8sAnomaliesRequestPayload = rt.TypeOf< + typeof getMetricsK8sAnomaliesRequestPayloadRT +>; diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/index.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/index.ts index a01042616a872..e696477253823 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/index.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/index.ts @@ -6,6 +6,7 @@ export * from './log_entry_categories'; export * from './log_entry_category_datasets'; +export * from './log_entry_category_datasets_stats'; export * from './log_entry_category_examples'; export * from './log_entry_rate'; export * from './log_entry_examples'; diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_category_datasets_stats.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_category_datasets_stats.ts new file mode 100644 index 0000000000000..4511678242f1c --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_category_datasets_stats.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { timeRangeRT, routeTimingMetadataRT } from '../../shared'; + +export const LOG_ANALYSIS_GET_LATEST_LOG_ENTRY_CATEGORY_DATASETS_STATS_PATH = + '/api/infra/log_analysis/results/latest_log_entry_category_datasets_stats'; + +const categorizerStatusRT = rt.keyof({ + ok: null, + warn: null, +}); + +export type CategorizerStatus = rt.TypeOf; + +/** + * request + */ + +export const getLatestLogEntryCategoryDatasetsStatsRequestPayloadRT = rt.type({ + data: rt.type({ + // the ids of the categorization jobs + jobIds: rt.array(rt.string), + // the time range to fetch the category datasets stats for + timeRange: timeRangeRT, + // the categorizer statuses to include stats for, empty means all + includeCategorizerStatuses: rt.array(categorizerStatusRT), + }), +}); + +export type GetLatestLogEntryCategoryDatasetsStatsRequestPayload = rt.TypeOf< + typeof getLatestLogEntryCategoryDatasetsStatsRequestPayloadRT +>; + +/** + * response + */ + +const logEntryCategoriesDatasetStatsRT = rt.type({ + categorization_status: categorizerStatusRT, + categorized_doc_count: rt.number, + dataset: rt.string, + dead_category_count: rt.number, + failed_category_count: rt.number, + frequent_category_count: rt.number, + job_id: rt.string, + log_time: rt.number, + rare_category_count: rt.number, + total_category_count: rt.number, +}); + +export type LogEntryCategoriesDatasetStats = rt.TypeOf; + +export const getLatestLogEntryCategoryDatasetsStatsSuccessResponsePayloadRT = rt.intersection([ + rt.type({ + data: rt.type({ + datasetStats: rt.array(logEntryCategoriesDatasetStatsRT), + }), + }), + rt.partial({ + timing: routeTimingMetadataRT, + }), +]); + +export type GetLatestLogEntryCategoryDatasetsStatsSuccessResponsePayload = rt.TypeOf< + typeof getLatestLogEntryCategoryDatasetsStatsSuccessResponsePayloadRT +>; diff --git a/x-pack/plugins/infra/common/http_api/snapshot_api.ts b/x-pack/plugins/infra/common/http_api/snapshot_api.ts index e1b8dfa4770ba..a6273fa967baf 100644 --- a/x-pack/plugins/infra/common/http_api/snapshot_api.ts +++ b/x-pack/plugins/infra/common/http_api/snapshot_api.ts @@ -99,7 +99,7 @@ export const SnapshotRequestRT = rt.intersection([ rt.type({ timerange: InfraTimerangeInputRT, metrics: rt.array(SnapshotMetricInputRT), - groupBy: SnapshotGroupByRT, + groupBy: rt.union([SnapshotGroupByRT, rt.null]), nodeType: ItemTypeRT, sourceId: rt.string, }), diff --git a/x-pack/plugins/infra/common/infra_ml/anomaly_results.ts b/x-pack/plugins/infra/common/infra_ml/anomaly_results.ts new file mode 100644 index 0000000000000..f4497dbba5056 --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/anomaly_results.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const ML_SEVERITY_SCORES = { + warning: 3, + minor: 25, + major: 50, + critical: 75, +}; + +export type MLSeverityScoreCategories = keyof typeof ML_SEVERITY_SCORES; + +export const ML_SEVERITY_COLORS = { + critical: 'rgb(228, 72, 72)', + major: 'rgb(229, 113, 0)', + minor: 'rgb(255, 221, 0)', + warning: 'rgb(125, 180, 226)', +}; + +export const getSeverityCategoryForScore = ( + score: number +): MLSeverityScoreCategories | undefined => { + if (score >= ML_SEVERITY_SCORES.critical) { + return 'critical'; + } else if (score >= ML_SEVERITY_SCORES.major) { + return 'major'; + } else if (score >= ML_SEVERITY_SCORES.minor) { + return 'minor'; + } else if (score >= ML_SEVERITY_SCORES.warning) { + return 'warning'; + } else { + // Category is too low to include + return undefined; + } +}; + +export const formatAnomalyScore = (score: number) => { + return Math.round(score); +}; + +export const formatOneDecimalPlace = (number: number) => { + return Math.round(number * 10) / 10; +}; + +export const getFriendlyNameForPartitionId = (partitionId: string) => { + return partitionId !== '' ? partitionId : 'unknown'; +}; + +export const compareDatasetsByMaximumAnomalyScore = < + Dataset extends { maximumAnomalyScore: number } +>( + firstDataset: Dataset, + secondDataset: Dataset +) => firstDataset.maximumAnomalyScore - secondDataset.maximumAnomalyScore; diff --git a/x-pack/plugins/security_solution/server/graphql/authentications/index.ts b/x-pack/plugins/infra/common/infra_ml/index.ts similarity index 59% rename from x-pack/plugins/security_solution/server/graphql/authentications/index.ts rename to x-pack/plugins/infra/common/infra_ml/index.ts index 8c16518590ad7..88fbbd4f25045 100644 --- a/x-pack/plugins/security_solution/server/graphql/authentications/index.ts +++ b/x-pack/plugins/infra/common/infra_ml/index.ts @@ -4,5 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createAuthenticationsResolvers } from './resolvers'; -export { authenticationsSchema } from './schema.gql'; +export * from './infra_ml'; +export * from './anomaly_results'; +export * from './job_parameters'; +export * from './metrics_hosts_ml'; +export * from './metrics_k8s_ml'; diff --git a/x-pack/plugins/infra/common/infra_ml/infra_ml.ts b/x-pack/plugins/infra/common/infra_ml/infra_ml.ts new file mode 100644 index 0000000000000..680a2a0fef114 --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/infra_ml.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// combines and abstracts job and datafeed status +export type JobStatus = + | 'unknown' + | 'missing' + | 'initializing' + | 'stopped' + | 'started' + | 'finished' + | 'failed'; + +export type SetupStatus = + | { type: 'initializing' } // acquiring job statuses to determine setup status + | { type: 'unknown' } // job status could not be acquired (failed request etc) + | { type: 'required' } // setup required + | { type: 'pending' } // In the process of setting up the module for the first time or retrying, waiting for response + | { type: 'succeeded' } // setup succeeded, notifying user + | { + type: 'failed'; + reasons: string[]; + } // setup failed, notifying user + | { + type: 'skipped'; + newlyCreated?: boolean; + }; // setup is not necessary + +/** + * Maps a job status to the possibility that results have already been produced + * before this state was reached. + */ +export const isJobStatusWithResults = (jobStatus: JobStatus) => + ['started', 'finished', 'stopped', 'failed'].includes(jobStatus); + +export const isHealthyJobStatus = (jobStatus: JobStatus) => + ['started', 'finished'].includes(jobStatus); + +/** + * Maps a setup status to the possibility that results have already been + * produced before this state was reached. + */ +export const isSetupStatusWithResults = (setupStatus: SetupStatus) => + setupStatus.type === 'skipped'; + +const KIBANA_SAMPLE_DATA_INDICES = ['kibana_sample_data_logs*']; + +export const isExampleDataIndex = (indexName: string) => + KIBANA_SAMPLE_DATA_INDICES.includes(indexName); diff --git a/x-pack/plugins/infra/common/infra_ml/job_parameters.ts b/x-pack/plugins/infra/common/infra_ml/job_parameters.ts new file mode 100644 index 0000000000000..8cd1c9ea6e2ba --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/job_parameters.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const bucketSpan = 900000; + +export const categoriesMessageField = 'message'; + +export const partitionField = 'event.dataset'; + +export const getJobIdPrefix = (spaceId: string, sourceId: string) => + `kibana-metrics-ui-${spaceId}-${sourceId}-`; + +export const getJobId = (spaceId: string, sourceId: string, jobType: string) => + `${getJobIdPrefix(spaceId, sourceId)}${jobType}`; + +export const getDatafeedId = (spaceId: string, sourceId: string, jobType: string) => + `datafeed-${getJobId(spaceId, sourceId, jobType)}`; + +export const datasetFilterRT = rt.union([ + rt.strict({ + type: rt.literal('includeAll'), + }), + rt.strict({ + type: rt.literal('includeSome'), + datasets: rt.array(rt.string), + }), +]); + +export type DatasetFilter = rt.TypeOf; + +export const jobSourceConfigurationRT = rt.partial({ + indexPattern: rt.string, + timestampField: rt.string, + bucketSpan: rt.number, + datasetFilter: datasetFilterRT, +}); + +export type JobSourceConfiguration = rt.TypeOf; + +export const jobCustomSettingsRT = rt.partial({ + job_revision: rt.number, + metrics_source_config: jobSourceConfigurationRT, +}); + +export type JobCustomSettings = rt.TypeOf; + +export const combineDatasetFilters = ( + firstFilter: DatasetFilter, + secondFilter: DatasetFilter +): DatasetFilter => { + if (firstFilter.type === 'includeAll' && secondFilter.type === 'includeAll') { + return { + type: 'includeAll', + }; + } + + const includedDatasets = new Set([ + ...(firstFilter.type === 'includeSome' ? firstFilter.datasets : []), + ...(secondFilter.type === 'includeSome' ? secondFilter.datasets : []), + ]); + + return { + type: 'includeSome', + datasets: [...includedDatasets], + }; +}; + +export const filterDatasetFilter = ( + datasetFilter: DatasetFilter, + predicate: (dataset: string) => boolean +): DatasetFilter => { + if (datasetFilter.type === 'includeAll') { + return datasetFilter; + } else { + const newDatasets = datasetFilter.datasets.filter(predicate); + + if (newDatasets.length > 0) { + return { + type: 'includeSome', + datasets: newDatasets, + }; + } else { + return { + type: 'includeAll', + }; + } + } +}; diff --git a/x-pack/plugins/infra/common/infra_ml/metrics_hosts_ml.ts b/x-pack/plugins/infra/common/infra_ml/metrics_hosts_ml.ts new file mode 100644 index 0000000000000..d09b3be78204f --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/metrics_hosts_ml.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const metricsHostsJobTypeRT = rt.keyof({ + hosts_memory_usage: null, + hosts_network_in: null, + hosts_network_out: null, +}); + +export type MetricsHostsJobType = rt.TypeOf; + +export const metricsHostsJobTypes: MetricsHostsJobType[] = [ + 'hosts_memory_usage', + 'hosts_network_in', + 'hosts_network_out', +]; diff --git a/x-pack/plugins/infra/common/infra_ml/metrics_k8s_ml.ts b/x-pack/plugins/infra/common/infra_ml/metrics_k8s_ml.ts new file mode 100644 index 0000000000000..3c27dbb61a14a --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/metrics_k8s_ml.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const metricK8sJobTypeRT = rt.keyof({ + k8s_memory_usage: null, + k8s_network_in: null, + k8s_network_out: null, +}); + +export type MetricK8sJobType = rt.TypeOf; + +export const metricsK8SJobTypes: MetricK8sJobType[] = [ + 'k8s_memory_usage', + 'k8s_network_in', + 'k8s_network_out', +]; diff --git a/x-pack/plugins/infra/common/inventory_models/aws_ec2/index.ts b/x-pack/plugins/infra/common/inventory_models/aws_ec2/index.ts index c12137f7810d4..6453332be4f50 100644 --- a/x-pack/plugins/infra/common/inventory_models/aws_ec2/index.ts +++ b/x-pack/plugins/infra/common/inventory_models/aws_ec2/index.ts @@ -31,4 +31,5 @@ export const awsEC2: InventoryModel = { }, requiredMetrics: ['awsEC2CpuUtilization', 'awsEC2NetworkTraffic', 'awsEC2DiskIOBytes'], tooltipMetrics: ['cpu', 'rx', 'tx'], + nodeFilter: [{ term: { 'event.dataset': 'aws.ec2' } }], }; diff --git a/x-pack/plugins/infra/common/inventory_models/intl_strings.ts b/x-pack/plugins/infra/common/inventory_models/intl_strings.ts index 2a885136f4ee7..cd7409100160d 100644 --- a/x-pack/plugins/infra/common/inventory_models/intl_strings.ts +++ b/x-pack/plugins/infra/common/inventory_models/intl_strings.ts @@ -5,36 +5,8 @@ */ import { i18n } from '@kbn/i18n'; -import { SnapshotMetricType } from './types'; -export const CPUUsage = i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', { - defaultMessage: 'CPU usage', -}); - -export const MemoryUsage = i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', { - defaultMessage: 'Memory usage', -}); - -export const InboundTraffic = i18n.translate( - 'xpack.infra.waffle.metricOptions.inboundTrafficText', - { - defaultMessage: 'Inbound traffic', - } -); - -export const OutboundTraffic = i18n.translate( - 'xpack.infra.waffle.metricOptions.outboundTrafficText', - { - defaultMessage: 'Outbound traffic', - } -); - -export const LogRate = i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', { - defaultMessage: 'Log rate', -}); - -export const Load = i18n.translate('xpack.infra.waffle.metricOptions.loadText', { - defaultMessage: 'Load', -}); +import { toMetricOpt } from '../snapshot_metric_i18n'; +import { SnapshotMetricType, SnapshotMetricTypeKeys } from './types'; interface Lookup { [id: string]: string; @@ -70,80 +42,9 @@ export const fieldToName = (field: string) => { return LOOKUP[field] || field; }; -export const SNAPSHOT_METRIC_TRANSLATIONS = { - cpu: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', { - defaultMessage: 'CPU usage', - }), - - memory: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', { - defaultMessage: 'Memory usage', - }), - - rx: i18n.translate('xpack.infra.waffle.metricOptions.inboundTrafficText', { - defaultMessage: 'Inbound traffic', - }), - - tx: i18n.translate('xpack.infra.waffle.metricOptions.outboundTrafficText', { - defaultMessage: 'Outbound traffic', - }), - - logRate: i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', { - defaultMessage: 'Log rate', - }), - - load: i18n.translate('xpack.infra.waffle.metricOptions.loadText', { - defaultMessage: 'Load', - }), - - count: i18n.translate('xpack.infra.waffle.metricOptions.countText', { - defaultMessage: 'Count', - }), - diskIOReadBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOReadBytes', { - defaultMessage: 'Disk Reads', - }), - diskIOWriteBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOWriteBytes', { - defaultMessage: 'Disk Writes', - }), - s3BucketSize: i18n.translate('xpack.infra.waffle.metricOptions.s3BucketSize', { - defaultMessage: 'Bucket Size', - }), - s3TotalRequests: i18n.translate('xpack.infra.waffle.metricOptions.s3TotalRequests', { - defaultMessage: 'Total Requests', - }), - s3NumberOfObjects: i18n.translate('xpack.infra.waffle.metricOptions.s3NumberOfObjects', { - defaultMessage: 'Number of Objects', - }), - s3DownloadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3DownloadBytes', { - defaultMessage: 'Downloads (Bytes)', - }), - s3UploadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3UploadBytes', { - defaultMessage: 'Uploads (Bytes)', - }), - rdsConnections: i18n.translate('xpack.infra.waffle.metricOptions.rdsConnections', { - defaultMessage: 'Connections', - }), - rdsQueriesExecuted: i18n.translate('xpack.infra.waffle.metricOptions.rdsQueriesExecuted', { - defaultMessage: 'Queries Executed', - }), - rdsActiveTransactions: i18n.translate('xpack.infra.waffle.metricOptions.rdsActiveTransactions', { - defaultMessage: 'Active Transactions', - }), - rdsLatency: i18n.translate('xpack.infra.waffle.metricOptions.rdsLatency', { - defaultMessage: 'Latency', - }), - sqsMessagesVisible: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesVisible', { - defaultMessage: 'Messages Available', - }), - sqsMessagesDelayed: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesDelayed', { - defaultMessage: 'Messages Delayed', - }), - sqsMessagesSent: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesSent', { - defaultMessage: 'Messages Added', - }), - sqsMessagesEmpty: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesEmpty', { - defaultMessage: 'Messages Returned Empty', - }), - sqsOldestMessage: i18n.translate('xpack.infra.waffle.metricOptions.sqsOldestMessage', { - defaultMessage: 'Oldest Message', - }), -} as Record; +const snapshotTypeKeys = Object.keys(SnapshotMetricTypeKeys) as SnapshotMetricType[]; +export const SNAPSHOT_METRIC_TRANSLATIONS = snapshotTypeKeys.reduce((result, metric) => { + const text = toMetricOpt(metric)?.text; + if (text) return { ...result, [metric]: text }; + return result; +}, {}) as Record; diff --git a/x-pack/plugins/infra/common/inventory_models/types.ts b/x-pack/plugins/infra/common/inventory_models/types.ts index 851646ef1fa12..5cc788f238365 100644 --- a/x-pack/plugins/infra/common/inventory_models/types.ts +++ b/x-pack/plugins/infra/common/inventory_models/types.ts @@ -314,7 +314,7 @@ export const ESAggregationRT = rt.union([ export const MetricsUIAggregationRT = rt.record(rt.string, ESAggregationRT); export type MetricsUIAggregation = rt.TypeOf; -export const SnapshotMetricTypeRT = rt.keyof({ +export const SnapshotMetricTypeKeys = { count: null, cpu: null, load: null, @@ -339,7 +339,8 @@ export const SnapshotMetricTypeRT = rt.keyof({ sqsMessagesEmpty: null, sqsOldestMessage: null, custom: null, -}); +}; +export const SnapshotMetricTypeRT = rt.keyof(SnapshotMetricTypeKeys); export type SnapshotMetricType = rt.TypeOf; @@ -370,4 +371,5 @@ export interface InventoryModel { metrics: InventoryMetrics; requiredMetrics: InventoryMetric[]; tooltipMetrics: SnapshotMetricType[]; + nodeFilter?: object[]; } diff --git a/x-pack/plugins/infra/common/log_analysis/index.ts b/x-pack/plugins/infra/common/log_analysis/index.ts index 22137e63ab7e7..0b4fa374a5da9 100644 --- a/x-pack/plugins/infra/common/log_analysis/index.ts +++ b/x-pack/plugins/infra/common/log_analysis/index.ts @@ -5,6 +5,7 @@ */ export * from './log_analysis'; +export * from './log_analysis_quality'; export * from './log_analysis_results'; export * from './log_entry_rate_analysis'; export * from './log_entry_categories_analysis'; diff --git a/x-pack/plugins/infra/common/log_analysis/log_analysis_quality.ts b/x-pack/plugins/infra/common/log_analysis/log_analysis_quality.ts new file mode 100644 index 0000000000000..7ffa6c172886b --- /dev/null +++ b/x-pack/plugins/infra/common/log_analysis/log_analysis_quality.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; + * you may not use this file except in compliance with the Elastic License. + */ + +interface ManyCategoriesWarningReason { + type: 'manyCategories'; + categoriesDocumentRatio: number; +} +interface ManyDeadCategoriesWarningReason { + type: 'manyDeadCategories'; + deadCategoriesRatio: number; +} +interface ManyRareCategoriesWarningReason { + type: 'manyRareCategories'; + rareCategoriesRatio: number; +} +interface NoFrequentCategoriesWarningReason { + type: 'noFrequentCategories'; +} +interface SingleCategoryWarningReason { + type: 'singleCategory'; +} + +export type CategoryQualityWarningReason = + | ManyCategoriesWarningReason + | ManyDeadCategoriesWarningReason + | ManyRareCategoriesWarningReason + | NoFrequentCategoriesWarningReason + | SingleCategoryWarningReason; + +export type CategoryQualityWarningReasonType = CategoryQualityWarningReason['type']; + +export interface CategoryQualityWarning { + type: 'categoryQualityWarning'; + jobId: string; + dataset: string; + reasons: CategoryQualityWarningReason[]; +} + +export type QualityWarning = CategoryQualityWarning; diff --git a/x-pack/plugins/infra/common/snapshot_metric_i18n.ts b/x-pack/plugins/infra/common/snapshot_metric_i18n.ts index 412c60fd9a1a7..60454e770584e 100644 --- a/x-pack/plugins/infra/common/snapshot_metric_i18n.ts +++ b/x-pack/plugins/infra/common/snapshot_metric_i18n.ts @@ -4,204 +4,235 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; +import { mapValues } from 'lodash'; import { SnapshotMetricType } from './inventory_models/types'; -const Translations = { +// Lowercase versions of all metrics, for when they need to be used in the middle of a sentence; +// these may need to be translated differently depending on language, e.g. still capitalizing "CPU" +const TranslationsLowercase = { CPUUsage: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', { defaultMessage: 'CPU usage', }), MemoryUsage: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', { - defaultMessage: 'Memory usage', + defaultMessage: 'memory usage', }), InboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.inboundTrafficText', { - defaultMessage: 'Inbound traffic', + defaultMessage: 'inbound traffic', }), OutboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.outboundTrafficText', { - defaultMessage: 'Outbound traffic', + defaultMessage: 'outbound traffic', }), LogRate: i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', { - defaultMessage: 'Log rate', + defaultMessage: 'log rate', }), Load: i18n.translate('xpack.infra.waffle.metricOptions.loadText', { - defaultMessage: 'Load', + defaultMessage: 'load', }), Count: i18n.translate('xpack.infra.waffle.metricOptions.countText', { - defaultMessage: 'Count', + defaultMessage: 'count', }), DiskIOReadBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOReadBytes', { - defaultMessage: 'Disk Reads', + defaultMessage: 'disk reads', }), DiskIOWriteBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOWriteBytes', { - defaultMessage: 'Disk Writes', + defaultMessage: 'disk writes', }), s3BucketSize: i18n.translate('xpack.infra.waffle.metricOptions.s3BucketSize', { - defaultMessage: 'Bucket Size', + defaultMessage: 'bucket size', }), s3TotalRequests: i18n.translate('xpack.infra.waffle.metricOptions.s3TotalRequests', { - defaultMessage: 'Total Requests', + defaultMessage: 'total requests', }), s3NumberOfObjects: i18n.translate('xpack.infra.waffle.metricOptions.s3NumberOfObjects', { - defaultMessage: 'Number of Objects', + defaultMessage: 'number of objects', }), s3DownloadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3DownloadBytes', { - defaultMessage: 'Downloads (Bytes)', + defaultMessage: 'downloads (bytes)', }), s3UploadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3UploadBytes', { - defaultMessage: 'Uploads (Bytes)', + defaultMessage: 'uploads (bytes)', }), rdsConnections: i18n.translate('xpack.infra.waffle.metricOptions.rdsConnections', { - defaultMessage: 'Connections', + defaultMessage: 'connections', }), rdsQueriesExecuted: i18n.translate('xpack.infra.waffle.metricOptions.rdsQueriesExecuted', { - defaultMessage: 'Queries Executed', + defaultMessage: 'queries executed', }), rdsActiveTransactions: i18n.translate('xpack.infra.waffle.metricOptions.rdsActiveTransactions', { - defaultMessage: 'Active Transactions', + defaultMessage: 'active transactions', }), rdsLatency: i18n.translate('xpack.infra.waffle.metricOptions.rdsLatency', { - defaultMessage: 'Latency', + defaultMessage: 'latency', }), sqsMessagesVisible: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesVisible', { - defaultMessage: 'Messages Available', + defaultMessage: 'messages available', }), sqsMessagesDelayed: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesDelayed', { - defaultMessage: 'Messages Delayed', + defaultMessage: 'messages delayed', }), sqsMessagesSent: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesSent', { - defaultMessage: 'Messages Added', + defaultMessage: 'messages added', }), sqsMessagesEmpty: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesEmpty', { - defaultMessage: 'Messages Returned Empty', + defaultMessage: 'messages returned empty', }), sqsOldestMessage: i18n.translate('xpack.infra.waffle.metricOptions.sqsOldestMessage', { - defaultMessage: 'Oldest Message', + defaultMessage: 'oldest message', }), }; +const Translations = mapValues( + TranslationsLowercase, + (translation) => `${translation[0].toUpperCase()}${translation.slice(1)}` +); + export const toMetricOpt = ( metric: SnapshotMetricType -): { text: string; value: SnapshotMetricType } | undefined => { +): { text: string; textLC: string; value: SnapshotMetricType } | undefined => { switch (metric) { case 'cpu': return { text: Translations.CPUUsage, + textLC: TranslationsLowercase.CPUUsage, value: 'cpu', }; case 'memory': return { text: Translations.MemoryUsage, + textLC: TranslationsLowercase.MemoryUsage, value: 'memory', }; case 'rx': return { text: Translations.InboundTraffic, + textLC: TranslationsLowercase.InboundTraffic, value: 'rx', }; case 'tx': return { text: Translations.OutboundTraffic, + textLC: TranslationsLowercase.OutboundTraffic, value: 'tx', }; case 'logRate': return { text: Translations.LogRate, + textLC: TranslationsLowercase.LogRate, value: 'logRate', }; case 'load': return { text: Translations.Load, + textLC: TranslationsLowercase.Load, value: 'load', }; case 'count': return { text: Translations.Count, + textLC: TranslationsLowercase.Count, value: 'count', }; case 'diskIOReadBytes': return { text: Translations.DiskIOReadBytes, + textLC: TranslationsLowercase.DiskIOReadBytes, value: 'diskIOReadBytes', }; case 'diskIOWriteBytes': return { text: Translations.DiskIOWriteBytes, + textLC: TranslationsLowercase.DiskIOWriteBytes, value: 'diskIOWriteBytes', }; case 's3BucketSize': return { text: Translations.s3BucketSize, + textLC: TranslationsLowercase.s3BucketSize, value: 's3BucketSize', }; case 's3TotalRequests': return { text: Translations.s3TotalRequests, + textLC: TranslationsLowercase.s3TotalRequests, value: 's3TotalRequests', }; case 's3NumberOfObjects': return { text: Translations.s3NumberOfObjects, + textLC: TranslationsLowercase.s3NumberOfObjects, value: 's3NumberOfObjects', }; case 's3DownloadBytes': return { text: Translations.s3DownloadBytes, + textLC: TranslationsLowercase.s3DownloadBytes, value: 's3DownloadBytes', }; case 's3UploadBytes': return { text: Translations.s3UploadBytes, + textLC: TranslationsLowercase.s3UploadBytes, value: 's3UploadBytes', }; case 'rdsConnections': return { text: Translations.rdsConnections, + textLC: TranslationsLowercase.rdsConnections, value: 'rdsConnections', }; case 'rdsQueriesExecuted': return { text: Translations.rdsQueriesExecuted, + textLC: TranslationsLowercase.rdsQueriesExecuted, value: 'rdsQueriesExecuted', }; case 'rdsActiveTransactions': return { text: Translations.rdsActiveTransactions, + textLC: TranslationsLowercase.rdsActiveTransactions, value: 'rdsActiveTransactions', }; case 'rdsLatency': return { text: Translations.rdsLatency, + textLC: TranslationsLowercase.rdsLatency, value: 'rdsLatency', }; case 'sqsMessagesVisible': return { text: Translations.sqsMessagesVisible, + textLC: TranslationsLowercase.sqsMessagesVisible, value: 'sqsMessagesVisible', }; case 'sqsMessagesDelayed': return { text: Translations.sqsMessagesDelayed, + textLC: TranslationsLowercase.sqsMessagesDelayed, value: 'sqsMessagesDelayed', }; case 'sqsMessagesSent': return { text: Translations.sqsMessagesSent, + textLC: TranslationsLowercase.sqsMessagesSent, value: 'sqsMessagesSent', }; case 'sqsMessagesEmpty': return { text: Translations.sqsMessagesEmpty, + textLC: TranslationsLowercase.sqsMessagesEmpty, value: 'sqsMessagesEmpty', }; case 'sqsOldestMessage': return { text: Translations.sqsOldestMessage, + textLC: TranslationsLowercase.sqsOldestMessage, value: 'sqsOldestMessage', }; } diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/job_configuration_outdated_callout.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/job_configuration_outdated_callout.tsx index 0489bd7d9929a..5b2ce862f7a81 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/job_configuration_outdated_callout.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/job_configuration_outdated_callout.tsx @@ -31,6 +31,7 @@ export const JobConfigurationOutdatedCallout: React.FC<{ values={{ moduleName, }} + tagName="p" /> ); diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/job_definition_outdated_callout.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/job_definition_outdated_callout.tsx index df9de49ea0445..b9e68b25482b6 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/job_definition_outdated_callout.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/job_definition_outdated_callout.tsx @@ -31,6 +31,7 @@ export const JobDefinitionOutdatedCallout: React.FC<{ values={{ moduleName, }} + tagName="p" /> ); diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/notices_section.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/notices_section.tsx index 2535058322cba..3785d0e8d9423 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/notices_section.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/notices_section.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { QualityWarning } from '../../../containers/logs/log_analysis/log_analysis_module_types'; +import { QualityWarning } from '../../../../common/log_analysis'; import { LogAnalysisJobProblemIndicator } from './log_analysis_job_problem_indicator'; import { CategoryQualityWarnings } from './quality_warning_notices'; @@ -41,6 +41,10 @@ export const CategoryJobNoticesSection: React.FC<{ onRecreateMlJobForReconfiguration={onRecreateMlJobForReconfiguration} onRecreateMlJobForUpdate={onRecreateMlJobForUpdate} /> - + ); diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/quality_warning_notices.stories.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/quality_warning_notices.stories.tsx new file mode 100644 index 0000000000000..7caf75417091a --- /dev/null +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/quality_warning_notices.stories.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { EuiThemeProvider } from '../../../../../observability/public'; +import { QualityWarning } from '../../../../common/log_analysis'; +import { CategoryQualityWarnings } from './quality_warning_notices'; + +storiesOf('infra/logAnalysis/CategoryQualityWarnings', module) + .addDecorator((renderStory) => {renderStory()}) + .add('Partitioned warnings', () => { + return ( + + ); + }) + .add('Unpartitioned warnings', () => { + return ( + + ); + }); + +const partitionedQualityWarnings: QualityWarning[] = [ + { + type: 'categoryQualityWarning', + jobId: 'theMlJobId', + dataset: 'first.dataset', + reasons: [ + { type: 'singleCategory' }, + { type: 'manyRareCategories', rareCategoriesRatio: 0.95 }, + { type: 'manyCategories', categoriesDocumentRatio: 0.7 }, + ], + }, + { + type: 'categoryQualityWarning', + jobId: 'theMlJobId', + dataset: 'second.dataset', + reasons: [ + { type: 'noFrequentCategories' }, + { type: 'manyDeadCategories', deadCategoriesRatio: 0.7 }, + ], + }, +]; + +const unpartitionedQualityWarnings: QualityWarning[] = [ + { + type: 'categoryQualityWarning', + jobId: 'theMlJobId', + dataset: '', + reasons: [ + { type: 'singleCategory' }, + { type: 'manyRareCategories', rareCategoriesRatio: 0.95 }, + { type: 'manyCategories', categoriesDocumentRatio: 0.7 }, + ], + }, +]; diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/quality_warning_notices.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/quality_warning_notices.tsx index 0d93ead5a82c6..4bf618923a138 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/quality_warning_notices.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/quality_warning_notices.tsx @@ -4,43 +4,89 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiCallOut } from '@elastic/eui'; +import { + EuiAccordion, + EuiDescriptionList, + EuiDescriptionListDescription, + EuiDescriptionListTitle, + EuiSpacer, + htmlIdGenerator, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; -import type { +import { groupBy } from 'lodash'; +import React, { Fragment, useState } from 'react'; +import { euiStyled } from '../../../../../observability/public'; +import { + CategoryQualityWarning, CategoryQualityWarningReason, - QualityWarning, -} from '../../../containers/logs/log_analysis/log_analysis_module_types'; + getFriendlyNameForPartitionId, +} from '../../../../common/log_analysis'; +import { RecreateJobCallout } from './recreate_job_callout'; -export const CategoryQualityWarnings: React.FC<{ qualityWarnings: QualityWarning[] }> = ({ - qualityWarnings, -}) => ( - <> - {qualityWarnings.map((qualityWarning, qualityWarningIndex) => ( - -

+export const CategoryQualityWarnings: React.FC<{ + hasSetupCapabilities: boolean; + onRecreateMlJob: () => void; + qualityWarnings: CategoryQualityWarning[]; +}> = ({ hasSetupCapabilities, onRecreateMlJob, qualityWarnings }) => { + const [detailAccordionId] = useState(htmlIdGenerator()()); + + const categoryQualityWarningsByJob = groupBy(qualityWarnings, 'jobId'); + + return ( + <> + {Object.entries(categoryQualityWarningsByJob).map(([jobId, qualityWarningsForJob]) => ( + -

-
    - {qualityWarning.reasons.map((reason, reasonIndex) => ( -
  • - -
  • - ))} -
-
- ))} - -); + + } + paddingSize="m" + > + + {qualityWarningsForJob.flatMap((qualityWarning) => ( + + + {getFriendlyNameForPartitionId(qualityWarning.dataset)} + + {qualityWarning.reasons.map((reason) => ( + + + + ))} + + ))} + + + + + ))} + + ); +}; + +const QualityWarningReasonDescription = euiStyled(EuiDescriptionListDescription)` + display: list-item; + list-style-type: disc; + margin-left: ${(props) => props.theme.eui.paddingSizes.m}; +`; const categoryQualityWarningCalloutTitle = i18n.translate( 'xpack.infra.logs.logEntryCategories.categoryQUalityWarningCalloutTitle', @@ -49,7 +95,7 @@ const categoryQualityWarningCalloutTitle = i18n.translate( } ); -const CategoryQualityWarningReasonDescription: React.FC<{ +export const CategoryQualityWarningReasonDescription: React.FC<{ reason: CategoryQualityWarningReason; }> = ({ reason }) => { switch (reason.type) { @@ -57,7 +103,7 @@ const CategoryQualityWarningReasonDescription: React.FC<{ return ( ); case 'manyRareCategories': diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx index cdf030a849fa1..2a0337bd99767 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx @@ -14,7 +14,7 @@ export const RecreateJobCallout: React.FC<{ title?: React.ReactNode; }> = ({ children, hasSetupCapabilities, onRecreateMlJob, title }) => ( -

{children}

+ {children} void; + previousQualityWarnings?: QualityWarning[]; validationErrors?: ValidationIndicesError[]; }> = ({ disabled = false, indices, isValidating, onChangeSelectedIndices, + previousQualityWarnings = [], validationErrors = [], }) => { const changeIsIndexSelected = useCallback( @@ -81,6 +84,7 @@ export const AnalysisSetupIndicesForm: React.FunctionComponent<{ key={index.name} onChangeIsSelected={changeIsIndexSelected} onChangeDatasetFilter={changeDatasetFilter} + previousQualityWarnings={previousQualityWarnings} /> ))} diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_dataset_filter.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_dataset_filter.tsx index d3ed8aeaf6155..481cc6071864c 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_dataset_filter.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_dataset_filter.tsx @@ -7,6 +7,7 @@ import { EuiFilterButton, EuiFilterGroup, + EuiIconTip, EuiPopover, EuiPopoverTitle, EuiSelectable, @@ -14,11 +15,15 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useCallback, useMemo } from 'react'; -import { DatasetFilter } from '../../../../../common/log_analysis'; +import { DatasetFilter, QualityWarning } from '../../../../../common/log_analysis'; import { useVisibilityState } from '../../../../utils/use_visibility_state'; +import { CategoryQualityWarningReasonDescription } from '../../log_analysis_job_status/quality_warning_notices'; export const IndexSetupDatasetFilter: React.FC<{ - availableDatasets: string[]; + availableDatasets: Array<{ + dataset: string; + warnings: QualityWarning[]; + }>; datasetFilter: DatasetFilter; isDisabled?: boolean; onChangeDatasetFilter: (datasetFilter: DatasetFilter) => void; @@ -40,12 +45,13 @@ export const IndexSetupDatasetFilter: React.FC<{ [onChangeDatasetFilter] ); - const selectableOptions: EuiSelectableOption[] = useMemo( + const selectableOptions = useMemo( () => - availableDatasets.map((datasetName) => ({ - label: datasetName, + availableDatasets.map(({ dataset, warnings }) => ({ + label: dataset, + append: warnings.length > 0 ? : null, checked: - datasetFilter.type === 'includeSome' && datasetFilter.datasets.includes(datasetName) + datasetFilter.type === 'includeSome' && datasetFilter.datasets.includes(dataset) ? 'on' : undefined, })), @@ -86,3 +92,15 @@ export const IndexSetupDatasetFilter: React.FC<{ ); }; + +const DatasetWarningMarker: React.FC<{ warnings: QualityWarning[] }> = ({ warnings }) => { + const warningDescriptions = warnings.flatMap((warning) => + warning.type === 'categoryQualityWarning' + ? warning.reasons.map((reason) => ( + + )) + : [] + ); + + return ; +}; diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_row.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_row.tsx index 92774dbd6838b..b101b9b0cab0c 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_row.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_row.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiCheckbox, EuiCode, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui'; +import { EuiCheckbox, EuiCode, EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useCallback } from 'react'; -import { DatasetFilter } from '../../../../../common/log_analysis'; +import React, { useCallback, useMemo } from 'react'; +import { DatasetFilter, QualityWarning } from '../../../../../common/log_analysis'; import { IndexSetupDatasetFilter } from './index_setup_dataset_filter'; import { AvailableIndex, ValidationUIError } from './validation'; @@ -16,7 +16,14 @@ export const IndexSetupRow: React.FC<{ isDisabled: boolean; onChangeDatasetFilter: (indexName: string, datasetFilter: DatasetFilter) => void; onChangeIsSelected: (indexName: string, isSelected: boolean) => void; -}> = ({ index, isDisabled, onChangeDatasetFilter, onChangeIsSelected }) => { + previousQualityWarnings: QualityWarning[]; +}> = ({ + index, + isDisabled, + onChangeDatasetFilter, + onChangeIsSelected, + previousQualityWarnings, +}) => { const changeIsSelected = useCallback( (event: React.ChangeEvent) => { onChangeIsSelected(index.name, event.currentTarget.checked); @@ -29,6 +36,29 @@ export const IndexSetupRow: React.FC<{ [index.name, onChangeDatasetFilter] ); + const datasets = useMemo( + () => + index.validity === 'valid' + ? index.availableDatasets.map((availableDataset) => ({ + dataset: availableDataset, + warnings: previousQualityWarnings.filter(({ dataset }) => dataset === availableDataset), + })) + : [], + [index, previousQualityWarnings] + ); + + const datasetIndependentQualityWarnings = useMemo( + () => previousQualityWarnings.filter(({ dataset }) => dataset === ''), + [previousQualityWarnings] + ); + + const hasWarnings = useMemo( + () => + datasetIndependentQualityWarnings.length > 0 || + datasets.some(({ warnings }) => warnings.length > 0), + [datasetIndependentQualityWarnings, datasets] + ); + const isSelected = index.validity === 'valid' && index.isSelected; return ( @@ -37,7 +67,23 @@ export const IndexSetupRow: React.FC<{ {index.name}} + label={ + <> + {index.name}{' '} + {index.validity === 'valid' && hasWarnings ? ( + + } + type="alert" + color="warning" + /> + ) : null} + + } onChange={changeIsSelected} checked={isSelected} disabled={isDisabled || index.validity === 'invalid'} @@ -45,12 +91,10 @@ export const IndexSetupRow: React.FC<{ {index.validity === 'invalid' ? ( - - - + ) : index.validity === 'valid' ? ( ( + +
{renderStory()}
+
+ )) + .add('Reconfiguration with partitioned warnings', () => { + return ( + + ); + }) + .add('Reconfiguration with unpartitioned warnings', () => { + return ( + + ); + }); + +const storyActions = actions('setStartTime', 'setEndTime', 'setValidatedIndices'); diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/initial_configuration_step.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/initial_configuration_step.tsx index d4c3c727bd34e..1ea972335d8fc 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/initial_configuration_step.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/initial_configuration_step.tsx @@ -9,7 +9,7 @@ import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useMemo } from 'react'; -import { SetupStatus } from '../../../../../common/log_analysis'; +import { QualityWarning, SetupStatus } from '../../../../../common/log_analysis'; import { AnalysisSetupIndicesForm } from './analysis_setup_indices_form'; import { AnalysisSetupTimerangeForm } from './analysis_setup_timerange_form'; import { @@ -31,6 +31,7 @@ interface InitialConfigurationStepProps { setupStatus: SetupStatus; setValidatedIndices: (selectedIndices: AvailableIndex[]) => void; validationErrors?: ValidationUIError[]; + previousQualityWarnings?: QualityWarning[]; } export const createInitialConfigurationStep = ( @@ -50,6 +51,7 @@ export const InitialConfigurationStep: React.FunctionComponent { const disabled = useMemo(() => !editableFormStatus.includes(setupStatus.type), [setupStatus]); @@ -75,6 +77,7 @@ export const InitialConfigurationStep: React.FunctionComponent diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/log_entry_categories_setup_view.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/log_entry_categories_setup_view.tsx index 2bc5b08a1016a..e7961a11a4d52 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/log_entry_categories_setup_view.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/log_entry_categories_setup_view.tsx @@ -6,6 +6,7 @@ import { EuiSpacer, EuiSteps, EuiText, EuiTitle } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; +import { useMount } from 'react-use'; import { useLogEntryCategoriesSetup } from '../../../../containers/logs/log_analysis/modules/log_entry_categories'; import { createInitialConfigurationStep } from '../initial_configuration_step'; import { createProcessStep } from '../process_step'; @@ -14,8 +15,10 @@ export const LogEntryCategoriesSetupView: React.FC<{ onClose: () => void; }> = ({ onClose }) => { const { + categoryQualityWarnings, cleanUpAndSetUp, endTime, + fetchJobStatus, isValidating, lastSetupErrorMessages, moduleDescriptor, @@ -30,6 +33,10 @@ export const LogEntryCategoriesSetupView: React.FC<{ viewResults, } = useLogEntryCategoriesSetup(); + useMount(() => { + fetchJobStatus(); + }); + const viewResultsAndClose = useCallback(() => { viewResults(); onClose(); @@ -47,6 +54,7 @@ export const LogEntryCategoriesSetupView: React.FC<{ setupStatus, setValidatedIndices, validationErrors, + previousQualityWarnings: categoryQualityWarnings, }), createProcessStep({ cleanUpAndSetUp, @@ -58,6 +66,7 @@ export const LogEntryCategoriesSetupView: React.FC<{ }), ], [ + categoryQualityWarnings, cleanUpAndSetUp, endTime, isValidating, diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout.tsx index 8e00254431438..407c851f2de95 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout.tsx @@ -15,14 +15,16 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { LogEntryRateSetupView } from './log_entry_rate_setup_view'; import { LogEntryCategoriesSetupView } from './log_entry_categories_setup_view'; +import { LogEntryRateSetupView } from './log_entry_rate_setup_view'; import { LogAnalysisModuleList } from './module_list'; -import { useLogAnalysisSetupFlyoutStateContext } from './setup_flyout_state'; +import { ModuleId, moduleIds, useLogAnalysisSetupFlyoutStateContext } from './setup_flyout_state'; const FLYOUT_HEADING_ID = 'logAnalysisSetupFlyoutHeading'; -export const LogAnalysisSetupFlyout: React.FC = () => { +export const LogAnalysisSetupFlyout: React.FC<{ + allowedModules?: ModuleId[]; +}> = ({ allowedModules = moduleIds }) => { const { closeFlyout, flyoutView, @@ -49,32 +51,58 @@ export const LogAnalysisSetupFlyout: React.FC = () => { {flyoutView.view === 'moduleList' ? ( - ) : flyoutView.view === 'moduleSetup' && flyoutView.module === 'logs_ui_analysis' ? ( - - - - ) : flyoutView.view === 'moduleSetup' && flyoutView.module === 'logs_ui_categories' ? ( - - - + ) : flyoutView.view === 'moduleSetup' && allowedModules.includes(flyoutView.module) ? ( + 1 ? showModuleList : undefined} + /> ) : null} ); }; +const ModuleSetupView: React.FC<{ + moduleId: ModuleId; + onClose: () => void; + onViewModuleList?: () => void; +}> = ({ moduleId, onClose, onViewModuleList }) => { + switch (moduleId) { + case 'logs_ui_analysis': + return ( + + + + ); + case 'logs_ui_categories': + return ( + + + + ); + } +}; + const LogAnalysisSetupFlyoutSubPage: React.FC<{ - onViewModuleList: () => void; + onViewModuleList?: () => void; }> = ({ children, onViewModuleList }) => ( - - - - - + {onViewModuleList ? ( + + + + + + ) : null} {children} ); diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout_state.ts b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout_state.ts index 7a64584df4303..5f131daf952bf 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout_state.ts +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout_state.ts @@ -9,6 +9,8 @@ import { useState, useCallback } from 'react'; export type ModuleId = 'logs_ui_analysis' | 'logs_ui_categories'; +export const moduleIds = ['logs_ui_analysis', 'logs_ui_categories'] as const; + type FlyoutView = | { view: 'hidden' } | { view: 'moduleList' } diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index 83fe233553351..698b0d3ad0caf 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -13,10 +13,9 @@ import { EuiDescriptionListTitle, EuiDescriptionListDescription, } from '@elastic/eui'; -import { EuiPopover } from '@elastic/eui'; +import { EuiPopover, EuiLink } from '@elastic/eui'; import { EuiListGroup, EuiListGroupItem } from '@elastic/eui'; import { EuiFlexItem } from '@elastic/eui'; -import { EuiButtonIcon } from '@elastic/eui'; import { SavedViewCreateModal } from './create_modal'; import { SavedViewUpdateModal } from './update_modal'; import { SavedViewManageViewsFlyout } from './manage_views_flyout'; @@ -151,15 +150,6 @@ export function SavedViewsToolbarControls(props: Props) { - - - (props: Props) { id="xpack.infra.savedView.currentView" /> - - {currentView - ? currentView.name - : i18n.translate('xpack.infra.savedView.unknownView', { - defaultMessage: 'No view selected', - })} - + + + {currentView + ? currentView.name + : i18n.translate('xpack.infra.savedView.unknownView', { + defaultMessage: 'No view selected', + })} + + diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/get_latest_categories_datasets_stats.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/get_latest_categories_datasets_stats.ts new file mode 100644 index 0000000000000..c095c7000f031 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/get_latest_categories_datasets_stats.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpHandler } from 'src/core/public'; +import { + CategorizerStatus, + getLatestLogEntryCategoryDatasetsStatsRequestPayloadRT, + getLatestLogEntryCategoryDatasetsStatsSuccessResponsePayloadRT, + LogEntryCategoriesDatasetStats, + LOG_ANALYSIS_GET_LATEST_LOG_ENTRY_CATEGORY_DATASETS_STATS_PATH, +} from '../../../../../common/http_api'; +import { decodeOrThrow } from '../../../../../common/runtime_types'; + +export { LogEntryCategoriesDatasetStats }; + +export const callGetLatestCategoriesDatasetsStatsAPI = async ( + { + jobIds, + startTime, + endTime, + includeCategorizerStatuses, + }: { + jobIds: string[]; + startTime: number; + endTime: number; + includeCategorizerStatuses: CategorizerStatus[]; + }, + fetch: HttpHandler +) => { + const response = await fetch(LOG_ANALYSIS_GET_LATEST_LOG_ENTRY_CATEGORY_DATASETS_STATS_PATH, { + method: 'POST', + body: JSON.stringify( + getLatestLogEntryCategoryDatasetsStatsRequestPayloadRT.encode({ + data: { + jobIds, + timeRange: { startTime, endTime }, + includeCategorizerStatuses, + }, + }) + ), + }); + + return decodeOrThrow(getLatestLogEntryCategoryDatasetsStatsSuccessResponsePayloadRT)(response); +}; diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts index dbd75a646b532..7441c0ab7d34c 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts @@ -54,6 +54,17 @@ const jobStateRT = rt.keyof({ opening: null, }); +const jobAnalysisConfigRT = rt.partial({ + per_partition_categorization: rt.intersection([ + rt.type({ + enabled: rt.boolean, + }), + rt.partial({ + stop_on_warn: rt.boolean, + }), + ]), +}); + const jobCategorizationStatusRT = rt.keyof({ ok: null, warn: null, @@ -64,6 +75,7 @@ const jobModelSizeStatsRT = rt.type({ categorized_doc_count: rt.number, dead_category_count: rt.number, frequent_category_count: rt.number, + log_time: rt.number, rare_category_count: rt.number, total_category_count: rt.number, }); @@ -79,6 +91,8 @@ export const jobSummaryRT = rt.intersection([ datafeedIndices: rt.array(rt.string), datafeedState: datafeedStateRT, fullJob: rt.partial({ + analysis_config: jobAnalysisConfigRT, + create_time: rt.number, custom_settings: jobCustomSettingsRT, finished_time: rt.number, model_size_stats: jobModelSizeStatsRT, diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts index 4930c8b478a9c..ba355ad195b11 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts @@ -50,43 +50,3 @@ export interface ModuleSourceConfiguration { spaceId: string; timestampField: string; } - -interface ManyCategoriesWarningReason { - type: 'manyCategories'; - categoriesDocumentRatio: number; -} - -interface ManyDeadCategoriesWarningReason { - type: 'manyDeadCategories'; - deadCategoriesRatio: number; -} - -interface ManyRareCategoriesWarningReason { - type: 'manyRareCategories'; - rareCategoriesRatio: number; -} - -interface NoFrequentCategoriesWarningReason { - type: 'noFrequentCategories'; -} - -interface SingleCategoryWarningReason { - type: 'singleCategory'; -} - -export type CategoryQualityWarningReason = - | ManyCategoriesWarningReason - | ManyDeadCategoriesWarningReason - | ManyRareCategoriesWarningReason - | NoFrequentCategoriesWarningReason - | SingleCategoryWarningReason; - -export type CategoryQualityWarningReasonType = CategoryQualityWarningReason['type']; - -export interface CategoryQualityWarning { - type: 'categoryQualityWarning'; - jobId: string; - reasons: CategoryQualityWarningReason[]; -} - -export type QualityWarning = CategoryQualityWarning; diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_quality.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_quality.ts index 346281fa94e1b..6bad94ec49f87 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_quality.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_quality.ts @@ -4,43 +4,124 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; +import { useDeepCompareEffect } from 'react-use'; import { - JobModelSizeStats, - JobSummary, - QualityWarning, CategoryQualityWarningReason, -} from '../../log_analysis_module_types'; + QualityWarning, +} from '../../../../../../common/log_analysis'; +import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; +import { useTrackedPromise } from '../../../../../utils/use_tracked_promise'; +import { + callGetLatestCategoriesDatasetsStatsAPI, + LogEntryCategoriesDatasetStats, +} from '../../api/get_latest_categories_datasets_stats'; +import { JobModelSizeStats, JobSummary } from '../../log_analysis_module_types'; export const useLogEntryCategoriesQuality = ({ jobSummaries }: { jobSummaries: JobSummary[] }) => { + const { + services: { + http: { fetch }, + }, + } = useKibanaContextForPlugin(); + + const [lastestWarnedDatasetsStats, setLatestWarnedDatasetsStats] = useState< + LogEntryCategoriesDatasetStats[] + >([]); + + const jobSummariesWithCategoryWarnings = useMemo( + () => jobSummaries.filter(isJobWithCategoryWarnings), + [jobSummaries] + ); + + const jobSummariesWithPartitionedCategoryWarnings = useMemo( + () => jobSummariesWithCategoryWarnings.filter(isJobWithPartitionedCategories), + [jobSummariesWithCategoryWarnings] + ); + + const [fetchLatestWarnedDatasetsStatsRequest, fetchLatestWarnedDatasetsStats] = useTrackedPromise( + { + cancelPreviousOn: 'creation', + createPromise: ( + statsIntervals: Array<{ jobId: string; startTime: number; endTime: number }> + ) => + Promise.all( + statsIntervals.map(({ jobId, startTime, endTime }) => + callGetLatestCategoriesDatasetsStatsAPI( + { jobIds: [jobId], startTime, endTime, includeCategorizerStatuses: ['warn'] }, + fetch + ) + ) + ), + onResolve: (results) => { + setLatestWarnedDatasetsStats(results.flatMap(({ data: { datasetStats } }) => datasetStats)); + }, + }, + [] + ); + + useDeepCompareEffect(() => { + fetchLatestWarnedDatasetsStats( + jobSummariesWithPartitionedCategoryWarnings.map((jobSummary) => ({ + jobId: jobSummary.id, + startTime: jobSummary.fullJob?.create_time ?? 0, + endTime: jobSummary.fullJob?.model_size_stats?.log_time ?? Date.now(), + })) + ); + }, [jobSummariesWithPartitionedCategoryWarnings]); + const categoryQualityWarnings: QualityWarning[] = useMemo( - () => - jobSummaries - .filter( - (jobSummary) => jobSummary.fullJob?.model_size_stats?.categorization_status === 'warn' - ) + () => [ + ...jobSummariesWithCategoryWarnings + .filter((jobSummary) => !isJobWithPartitionedCategories(jobSummary)) .map((jobSummary) => ({ - type: 'categoryQualityWarning', + type: 'categoryQualityWarning' as const, jobId: jobSummary.id, + dataset: '', reasons: jobSummary.fullJob?.model_size_stats ? getCategoryQualityWarningReasons(jobSummary.fullJob.model_size_stats) : [], })), - [jobSummaries] + ...lastestWarnedDatasetsStats.map((datasetStats) => ({ + type: 'categoryQualityWarning' as const, + jobId: datasetStats.job_id, + dataset: datasetStats.dataset, + reasons: getCategoryQualityWarningReasons(datasetStats), + })), + ], + [jobSummariesWithCategoryWarnings, lastestWarnedDatasetsStats] ); return { categoryQualityWarnings, + lastLatestWarnedDatasetsStatsRequestErrors: + fetchLatestWarnedDatasetsStatsRequest.state === 'rejected' + ? fetchLatestWarnedDatasetsStatsRequest.value + : null, + isLoadingCategoryQualityWarnings: fetchLatestWarnedDatasetsStatsRequest.state === 'pending', }; }; +const isJobWithCategoryWarnings = (jobSummary: JobSummary) => + jobSummary.fullJob?.model_size_stats?.categorization_status === 'warn'; + +const isJobWithPartitionedCategories = (jobSummary: JobSummary) => + jobSummary.fullJob?.analysis_config?.per_partition_categorization ?? false; + const getCategoryQualityWarningReasons = ({ categorized_doc_count: categorizedDocCount, dead_category_count: deadCategoryCount, frequent_category_count: frequentCategoryCount, rare_category_count: rareCategoryCount, total_category_count: totalCategoryCount, -}: JobModelSizeStats): CategoryQualityWarningReason[] => { +}: Pick< + JobModelSizeStats, + | 'categorized_doc_count' + | 'dead_category_count' + | 'frequent_category_count' + | 'rare_category_count' + | 'total_category_count' +>): CategoryQualityWarningReason[] => { const rareCategoriesRatio = rareCategoryCount / totalCategoryCount; const categoriesDocumentRatio = totalCategoryCount / categorizedDocCount; const deadCategoriesRatio = deadCategoryCount / totalCategoryCount; diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_setup.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_setup.tsx index 399c30cf47e71..269b64c6f4076 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_setup.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_setup.tsx @@ -9,7 +9,9 @@ import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_m export const useLogEntryCategoriesSetup = () => { const { + categoryQualityWarnings, cleanUpAndSetUpModule, + fetchJobStatus, lastSetupErrorMessages, moduleDescriptor, setUpModule, @@ -37,8 +39,10 @@ export const useLogEntryCategoriesSetup = () => { }); return { + categoryQualityWarnings, cleanUpAndSetUp, endTime, + fetchJobStatus, isValidating, lastSetupErrorMessages, moduleDescriptor, diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_api_types.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_api_types.ts new file mode 100644 index 0000000000000..ee70edc31d49b --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/api/ml_api_types.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const getMlCapabilitiesResponsePayloadRT = rt.type({ + capabilities: rt.type({ + canGetJobs: rt.boolean, + canCreateJob: rt.boolean, + canDeleteJob: rt.boolean, + canOpenJob: rt.boolean, + canCloseJob: rt.boolean, + canForecastJob: rt.boolean, + canGetDatafeeds: rt.boolean, + canStartStopDatafeed: rt.boolean, + canUpdateJob: rt.boolean, + canUpdateDatafeed: rt.boolean, + canPreviewDatafeed: rt.boolean, + }), + isPlatinumOrTrialLicense: rt.boolean, + mlFeatureEnabledInSpace: rt.boolean, + upgradeInProgress: rt.boolean, +}); + +export type GetMlCapabilitiesResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts new file mode 100644 index 0000000000000..23fa338e74f14 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { npStart } from '../../../legacy_singletons'; + +import { getDatafeedId, getJobId } from '../../../../common/infra_ml'; +import { throwErrors, createPlainError } from '../../../../common/runtime_types'; + +export const callDeleteJobs = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + // NOTE: Deleting the jobs via this API will delete the datafeeds at the same time + const deleteJobsResponse = await npStart.http.fetch('/api/ml/jobs/delete_jobs', { + method: 'POST', + body: JSON.stringify( + deleteJobsRequestPayloadRT.encode({ + jobIds: jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType)), + }) + ), + }); + + return pipe( + deleteJobsResponsePayloadRT.decode(deleteJobsResponse), + fold(throwErrors(createPlainError), identity) + ); +}; + +export const callGetJobDeletionTasks = async () => { + const jobDeletionTasksResponse = await npStart.http.fetch('/api/ml/jobs/deleting_jobs_tasks'); + + return pipe( + getJobDeletionTasksResponsePayloadRT.decode(jobDeletionTasksResponse), + fold(throwErrors(createPlainError), identity) + ); +}; + +export const callStopDatafeeds = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + // Stop datafeed due to https://github.com/elastic/kibana/issues/44652 + const stopDatafeedResponse = await npStart.http.fetch('/api/ml/jobs/stop_datafeeds', { + method: 'POST', + body: JSON.stringify( + stopDatafeedsRequestPayloadRT.encode({ + datafeedIds: jobTypes.map((jobType) => getDatafeedId(spaceId, sourceId, jobType)), + }) + ), + }); + + return pipe( + stopDatafeedsResponsePayloadRT.decode(stopDatafeedResponse), + fold(throwErrors(createPlainError), identity) + ); +}; + +export const deleteJobsRequestPayloadRT = rt.type({ + jobIds: rt.array(rt.string), +}); + +export type DeleteJobsRequestPayload = rt.TypeOf; + +export const deleteJobsResponsePayloadRT = rt.record( + rt.string, + rt.type({ + deleted: rt.boolean, + }) +); + +export type DeleteJobsResponsePayload = rt.TypeOf; + +export const getJobDeletionTasksResponsePayloadRT = rt.type({ + jobIds: rt.array(rt.string), +}); + +export const stopDatafeedsRequestPayloadRT = rt.type({ + datafeedIds: rt.array(rt.string), +}); + +export const stopDatafeedsResponsePayloadRT = rt.record( + rt.string, + rt.type({ + stopped: rt.boolean, + }) +); diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_get_jobs_summary_api.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_get_jobs_summary_api.ts new file mode 100644 index 0000000000000..3fddb63f69791 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/api/ml_get_jobs_summary_api.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; +import { npStart } from '../../../legacy_singletons'; + +import { getJobId, jobCustomSettingsRT } from '../../../../common/infra_ml'; +import { createPlainError, throwErrors } from '../../../../common/runtime_types'; + +export const callJobsSummaryAPI = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + const response = await npStart.http.fetch('/api/ml/jobs/jobs_summary', { + method: 'POST', + body: JSON.stringify( + fetchJobStatusRequestPayloadRT.encode({ + jobIds: jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType)), + }) + ), + }); + return pipe( + fetchJobStatusResponsePayloadRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); +}; + +export const fetchJobStatusRequestPayloadRT = rt.type({ + jobIds: rt.array(rt.string), +}); + +export type FetchJobStatusRequestPayload = rt.TypeOf; + +const datafeedStateRT = rt.keyof({ + started: null, + stopped: null, + stopping: null, + '': null, +}); + +const jobStateRT = rt.keyof({ + closed: null, + closing: null, + deleting: null, + failed: null, + opened: null, + opening: null, +}); + +const jobCategorizationStatusRT = rt.keyof({ + ok: null, + warn: null, +}); + +const jobModelSizeStatsRT = rt.type({ + categorization_status: jobCategorizationStatusRT, + categorized_doc_count: rt.number, + dead_category_count: rt.number, + frequent_category_count: rt.number, + rare_category_count: rt.number, + total_category_count: rt.number, +}); + +export type JobModelSizeStats = rt.TypeOf; + +export const jobSummaryRT = rt.intersection([ + rt.type({ + id: rt.string, + jobState: jobStateRT, + }), + rt.partial({ + datafeedIndices: rt.array(rt.string), + datafeedState: datafeedStateRT, + fullJob: rt.partial({ + custom_settings: jobCustomSettingsRT, + finished_time: rt.number, + model_size_stats: jobModelSizeStatsRT, + }), + }), +]); + +export type JobSummary = rt.TypeOf; + +export const fetchJobStatusResponsePayloadRT = rt.array(jobSummaryRT); + +export type FetchJobStatusResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_get_module.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_get_module.ts new file mode 100644 index 0000000000000..d492522c120a1 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/api/ml_get_module.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; +import { npStart } from '../../../legacy_singletons'; + +import { jobCustomSettingsRT } from '../../../../common/log_analysis'; +import { createPlainError, throwErrors } from '../../../../common/runtime_types'; + +export const callGetMlModuleAPI = async (moduleId: string) => { + const response = await npStart.http.fetch(`/api/ml/modules/get_module/${moduleId}`, { + method: 'GET', + }); + + return pipe( + getMlModuleResponsePayloadRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); +}; + +const jobDefinitionRT = rt.type({ + id: rt.string, + config: rt.type({ + custom_settings: jobCustomSettingsRT, + }), +}); + +export type JobDefinition = rt.TypeOf; + +const getMlModuleResponsePayloadRT = rt.type({ + id: rt.string, + jobs: rt.array(jobDefinitionRT), +}); + +export type GetMlModuleResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_setup_module_api.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_setup_module_api.ts new file mode 100644 index 0000000000000..06b0e075387b0 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/api/ml_setup_module_api.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; +import { npStart } from '../../../legacy_singletons'; + +import { getJobIdPrefix, jobCustomSettingsRT } from '../../../../common/infra_ml'; +import { createPlainError, throwErrors } from '../../../../common/runtime_types'; + +export const callSetupMlModuleAPI = async ( + moduleId: string, + start: number | undefined, + end: number | undefined, + spaceId: string, + sourceId: string, + indexPattern: string, + jobOverrides: SetupMlModuleJobOverrides[] = [], + datafeedOverrides: SetupMlModuleDatafeedOverrides[] = [], + query?: object +) => { + const response = await npStart.http.fetch(`/api/ml/modules/setup/${moduleId}`, { + method: 'POST', + body: JSON.stringify( + setupMlModuleRequestPayloadRT.encode({ + start, + end, + indexPatternName: indexPattern, + prefix: getJobIdPrefix(spaceId, sourceId), + startDatafeed: true, + jobOverrides, + datafeedOverrides, + query, + }) + ), + }); + + return pipe( + setupMlModuleResponsePayloadRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); +}; + +const setupMlModuleTimeParamsRT = rt.partial({ + start: rt.number, + end: rt.number, +}); + +const setupMlModuleJobOverridesRT = rt.type({ + job_id: rt.string, + custom_settings: jobCustomSettingsRT, +}); + +export type SetupMlModuleJobOverrides = rt.TypeOf; + +const setupMlModuleDatafeedOverridesRT = rt.object; + +export type SetupMlModuleDatafeedOverrides = rt.TypeOf; + +const setupMlModuleRequestParamsRT = rt.intersection([ + rt.strict({ + indexPatternName: rt.string, + prefix: rt.string, + startDatafeed: rt.boolean, + jobOverrides: rt.array(setupMlModuleJobOverridesRT), + datafeedOverrides: rt.array(setupMlModuleDatafeedOverridesRT), + }), + rt.exact( + rt.partial({ + query: rt.object, + }) + ), +]); + +const setupMlModuleRequestPayloadRT = rt.intersection([ + setupMlModuleTimeParamsRT, + setupMlModuleRequestParamsRT, +]); + +const setupErrorResponseRT = rt.type({ + msg: rt.string, +}); + +const datafeedSetupResponseRT = rt.intersection([ + rt.type({ + id: rt.string, + started: rt.boolean, + success: rt.boolean, + }), + rt.partial({ + error: setupErrorResponseRT, + }), +]); + +const jobSetupResponseRT = rt.intersection([ + rt.type({ + id: rt.string, + success: rt.boolean, + }), + rt.partial({ + error: setupErrorResponseRT, + }), +]); + +const setupMlModuleResponsePayloadRT = rt.type({ + datafeeds: rt.array(datafeedSetupResponseRT), + jobs: rt.array(jobSetupResponseRT), +}); + +export type SetupMlModuleResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx new file mode 100644 index 0000000000000..f4c90a459af6a --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import createContainer from 'constate'; +import { useMemo, useState, useEffect } from 'react'; +import { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { identity } from 'fp-ts/lib/function'; +import { useTrackedPromise } from '../../utils/use_tracked_promise'; +import { npStart } from '../../legacy_singletons'; +import { + getMlCapabilitiesResponsePayloadRT, + GetMlCapabilitiesResponsePayload, +} from './api/ml_api_types'; +import { throwErrors, createPlainError } from '../../../common/runtime_types'; + +export const useInfraMLCapabilities = () => { + const [mlCapabilities, setMlCapabilities] = useState( + initialMlCapabilities + ); + + const [fetchMlCapabilitiesRequest, fetchMlCapabilities] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + const rawResponse = await npStart.http.fetch('/api/ml/ml_capabilities'); + + return pipe( + getMlCapabilitiesResponsePayloadRT.decode(rawResponse), + fold(throwErrors(createPlainError), identity) + ); + }, + onResolve: (response) => { + setMlCapabilities(response); + }, + }, + [] + ); + + useEffect(() => { + fetchMlCapabilities(); + }, [fetchMlCapabilities]); + + const isLoading = useMemo(() => fetchMlCapabilitiesRequest.state === 'pending', [ + fetchMlCapabilitiesRequest.state, + ]); + + const hasInfraMLSetupCapabilities = mlCapabilities.capabilities.canCreateJob; + const hasInfraMLReadCapabilities = mlCapabilities.capabilities.canGetJobs; + const hasInfraMLCapabilites = + mlCapabilities.isPlatinumOrTrialLicense && mlCapabilities.mlFeatureEnabledInSpace; + + return { + hasInfraMLCapabilites, + hasInfraMLReadCapabilities, + hasInfraMLSetupCapabilities, + isLoading, + }; +}; + +export const [InfraMLCapabilitiesProvider, useInfraMLCapabilitiesContext] = createContainer( + useInfraMLCapabilities +); + +const initialMlCapabilities = { + capabilities: { + canGetJobs: false, + canCreateJob: false, + canDeleteJob: false, + canOpenJob: false, + canCloseJob: false, + canForecastJob: false, + canGetDatafeeds: false, + canStartStopDatafeed: false, + canUpdateJob: false, + canUpdateDatafeed: false, + canPreviewDatafeed: false, + canGetCalendars: false, + canCreateCalendar: false, + canDeleteCalendar: false, + canGetFilters: false, + canCreateFilter: false, + canDeleteFilter: false, + canFindFileStructure: false, + canGetDataFrameJobs: false, + canDeleteDataFrameJob: false, + canPreviewDataFrameJob: false, + canCreateDataFrameJob: false, + canStartStopDataFrameJob: false, + }, + isPlatinumOrTrialLicense: false, + mlFeatureEnabledInSpace: false, + upgradeInProgress: false, +}; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_cleanup.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_cleanup.tsx new file mode 100644 index 0000000000000..736982c8043b1 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_cleanup.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getJobId } from '../../../common/infra_ml'; +import { callDeleteJobs, callGetJobDeletionTasks, callStopDatafeeds } from './api/ml_cleanup'; + +export const cleanUpJobsAndDatafeeds = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + try { + await callStopDatafeeds(spaceId, sourceId, jobTypes); + } catch (err) { + // Proceed only if datafeed has been deleted or didn't exist in the first place + if (err?.res?.status !== 404) { + throw err; + } + } + + return await deleteJobs(spaceId, sourceId, jobTypes); +}; + +const deleteJobs = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + const deleteJobsResponse = await callDeleteJobs(spaceId, sourceId, jobTypes); + await waitUntilJobsAreDeleted(spaceId, sourceId, jobTypes); + return deleteJobsResponse; +}; + +const waitUntilJobsAreDeleted = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + const moduleJobIds = jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType)); + while (true) { + const { jobIds: jobIdsBeingDeleted } = await callGetJobDeletionTasks(); + const needToWait = jobIdsBeingDeleted.some((jobId) => moduleJobIds.includes(jobId)); + + if (needToWait) { + await timeout(1000); + } else { + return true; + } + } +}; + +const timeout = (ms: number) => new Promise((res) => setTimeout(res, ms)); diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx new file mode 100644 index 0000000000000..349541d108f5e --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useCallback, useMemo } from 'react'; +import { DatasetFilter } from '../../../common/infra_ml'; +import { useTrackedPromise } from '../../utils/use_tracked_promise'; +import { useModuleStatus } from './infra_ml_module_status'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './infra_ml_module_types'; + +export const useInfraMLModule = ({ + sourceConfiguration, + moduleDescriptor, +}: { + sourceConfiguration: ModuleSourceConfiguration; + moduleDescriptor: ModuleDescriptor; +}) => { + const { spaceId, sourceId, timestampField } = sourceConfiguration; + const [moduleStatus, dispatchModuleStatus] = useModuleStatus(moduleDescriptor.jobTypes); + + const [, fetchJobStatus] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + dispatchModuleStatus({ type: 'fetchingJobStatuses' }); + return await moduleDescriptor.getJobSummary(spaceId, sourceId); + }, + onResolve: (jobResponse) => { + dispatchModuleStatus({ + type: 'fetchedJobStatuses', + payload: jobResponse, + spaceId, + sourceId, + }); + }, + onReject: () => { + dispatchModuleStatus({ type: 'failedFetchingJobStatuses' }); + }, + }, + [spaceId, sourceId] + ); + + const [, setUpModule] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async ( + selectedIndices: string[], + start: number | undefined, + end: number | undefined, + datasetFilter: DatasetFilter, + partitionField?: string + ) => { + dispatchModuleStatus({ type: 'startedSetup' }); + const setupResult = await moduleDescriptor.setUpModule( + start, + end, + datasetFilter, + { + indices: selectedIndices, + sourceId, + spaceId, + timestampField, + }, + partitionField + ); + const jobSummaries = await moduleDescriptor.getJobSummary(spaceId, sourceId); + return { setupResult, jobSummaries }; + }, + onResolve: ({ setupResult: { datafeeds, jobs }, jobSummaries }) => { + dispatchModuleStatus({ + type: 'finishedSetup', + datafeedSetupResults: datafeeds, + jobSetupResults: jobs, + jobSummaries, + spaceId, + sourceId, + }); + }, + onReject: () => { + dispatchModuleStatus({ type: 'failedSetup' }); + }, + }, + [moduleDescriptor.setUpModule, spaceId, sourceId, timestampField] + ); + + const [cleanUpModuleRequest, cleanUpModule] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await moduleDescriptor.cleanUpModule(spaceId, sourceId); + }, + }, + [spaceId, sourceId] + ); + + const isCleaningUp = useMemo(() => cleanUpModuleRequest.state === 'pending', [ + cleanUpModuleRequest.state, + ]); + + const cleanUpAndSetUpModule = useCallback( + ( + selectedIndices: string[], + start: number | undefined, + end: number | undefined, + datasetFilter: DatasetFilter, + partitionField?: string + ) => { + dispatchModuleStatus({ type: 'startedSetup' }); + cleanUpModule() + .then(() => { + setUpModule(selectedIndices, start, end, datasetFilter, partitionField); + }) + .catch(() => { + dispatchModuleStatus({ type: 'failedSetup' }); + }); + }, + [cleanUpModule, dispatchModuleStatus, setUpModule] + ); + + const viewResults = useCallback(() => { + dispatchModuleStatus({ type: 'viewedResults' }); + }, [dispatchModuleStatus]); + + const jobIds = useMemo(() => moduleDescriptor.getJobIds(spaceId, sourceId), [ + moduleDescriptor, + spaceId, + sourceId, + ]); + + return { + cleanUpAndSetUpModule, + cleanUpModule, + fetchJobStatus, + isCleaningUp, + jobIds, + jobStatus: moduleStatus.jobStatus, + jobSummaries: moduleStatus.jobSummaries, + lastSetupErrorMessages: moduleStatus.lastSetupErrorMessages, + moduleDescriptor, + setUpModule, + setupStatus: moduleStatus.setupStatus, + sourceConfiguration, + viewResults, + }; +}; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts new file mode 100644 index 0000000000000..2d90f5d531010 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useMemo } from 'react'; +import { JobSummary } from './api/ml_get_jobs_summary_api'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './infra_ml_module_types'; + +export const useInfraMLModuleConfiguration = ({ + moduleDescriptor, + sourceConfiguration, +}: { + moduleDescriptor: ModuleDescriptor; + sourceConfiguration: ModuleSourceConfiguration; +}) => { + const getIsJobConfigurationOutdated = useMemo( + () => isJobConfigurationOutdated(moduleDescriptor, sourceConfiguration), + [sourceConfiguration, moduleDescriptor] + ); + + return { + getIsJobConfigurationOutdated, + }; +}; + +export const isJobConfigurationOutdated = ( + { bucketSpan }: ModuleDescriptor, + currentSourceConfiguration: ModuleSourceConfiguration +) => (jobSummary: JobSummary): boolean => { + if (!jobSummary.fullJob || !jobSummary.fullJob.custom_settings) { + return false; + } + + const jobConfiguration = jobSummary.fullJob.custom_settings.metrics_source_config; + + return !( + jobConfiguration && + jobConfiguration.bucketSpan === bucketSpan && + jobConfiguration.indexPattern && + isSubset( + new Set(jobConfiguration.indexPattern.split(',')), + new Set(currentSourceConfiguration.indices) + ) && + jobConfiguration.timestampField === currentSourceConfiguration.timestampField + ); +}; + +const isSubset = (subset: Set, superset: Set) => { + return Array.from(subset).every((subsetElement) => superset.has(subsetElement)); +}; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_definition.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_definition.tsx new file mode 100644 index 0000000000000..3c7ffcfd4a4e2 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_definition.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useCallback, useMemo, useState } from 'react'; +import { getJobId } from '../../../common/log_analysis'; +import { useTrackedPromise } from '../../utils/use_tracked_promise'; +import { JobSummary } from './api/ml_get_jobs_summary_api'; +import { GetMlModuleResponsePayload, JobDefinition } from './api/ml_get_module'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './infra_ml_module_types'; + +export const useInfraMLModuleDefinition = ({ + sourceConfiguration: { spaceId, sourceId }, + moduleDescriptor, +}: { + sourceConfiguration: ModuleSourceConfiguration; + moduleDescriptor: ModuleDescriptor; +}) => { + const [moduleDefinition, setModuleDefinition] = useState< + GetMlModuleResponsePayload | undefined + >(); + + const jobDefinitionByJobId = useMemo( + () => + moduleDefinition + ? moduleDefinition.jobs.reduce>( + (accumulatedJobDefinitions, jobDefinition) => ({ + ...accumulatedJobDefinitions, + [getJobId(spaceId, sourceId, jobDefinition.id)]: jobDefinition, + }), + {} + ) + : {}, + [moduleDefinition, sourceId, spaceId] + ); + + const [fetchModuleDefinitionRequest, fetchModuleDefinition] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await moduleDescriptor.getModuleDefinition(); + }, + onResolve: (response) => { + setModuleDefinition(response); + }, + onReject: () => { + setModuleDefinition(undefined); + }, + }, + [moduleDescriptor.getModuleDefinition, spaceId, sourceId] + ); + + const getIsJobDefinitionOutdated = useCallback( + (jobSummary: JobSummary): boolean => { + const jobDefinition: JobDefinition | undefined = jobDefinitionByJobId[jobSummary.id]; + + if (jobDefinition == null) { + return false; + } + + const currentRevision = jobDefinition?.config.custom_settings.job_revision; + return (jobSummary.fullJob?.custom_settings?.job_revision ?? 0) < (currentRevision ?? 0); + }, + [jobDefinitionByJobId] + ); + + return { + fetchModuleDefinition, + fetchModuleDefinitionRequestState: fetchModuleDefinitionRequest.state, + getIsJobDefinitionOutdated, + jobDefinitionByJobId, + moduleDefinition, + }; +}; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_status.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_status.tsx new file mode 100644 index 0000000000000..63d479546b44f --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_status.tsx @@ -0,0 +1,268 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useReducer } from 'react'; + +import { + JobStatus, + getDatafeedId, + getJobId, + isJobStatusWithResults, + SetupStatus, +} from '../../../common/infra_ml'; +import { FetchJobStatusResponsePayload, JobSummary } from './api/ml_get_jobs_summary_api'; +import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; +import { MandatoryProperty } from '../../../common/utility_types'; + +interface StatusReducerState { + jobStatus: Record; + jobSummaries: JobSummary[]; + lastSetupErrorMessages: string[]; + setupStatus: SetupStatus; +} + +type StatusReducerAction = + | { type: 'startedSetup' } + | { + type: 'finishedSetup'; + sourceId: string; + spaceId: string; + jobSetupResults: SetupMlModuleResponsePayload['jobs']; + jobSummaries: FetchJobStatusResponsePayload; + datafeedSetupResults: SetupMlModuleResponsePayload['datafeeds']; + } + | { type: 'failedSetup' } + | { type: 'fetchingJobStatuses' } + | { + type: 'fetchedJobStatuses'; + spaceId: string; + sourceId: string; + payload: FetchJobStatusResponsePayload; + } + | { type: 'failedFetchingJobStatuses' } + | { type: 'viewedResults' }; + +const createInitialState = ({ + jobTypes, +}: { + jobTypes: JobType[]; +}): StatusReducerState => ({ + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'unknown', + }), + {} as Record + ), + jobSummaries: [], + lastSetupErrorMessages: [], + setupStatus: { type: 'initializing' }, +}); + +const createStatusReducer = (jobTypes: JobType[]) => ( + state: StatusReducerState, + action: StatusReducerAction +): StatusReducerState => { + switch (action.type) { + case 'startedSetup': { + return { + ...state, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'initializing', + }), + {} as Record + ), + setupStatus: { type: 'pending' }, + }; + } + case 'finishedSetup': { + const { datafeedSetupResults, jobSetupResults, jobSummaries, spaceId, sourceId } = action; + const nextJobStatus = jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: + hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, jobType))(jobSetupResults) && + hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, jobType))( + datafeedSetupResults + ) + ? 'started' + : 'failed', + }), + {} as Record + ); + const nextSetupStatus: SetupStatus = Object.values(nextJobStatus).every( + (jobState) => jobState === 'started' + ) + ? { type: 'succeeded' } + : { + type: 'failed', + reasons: [ + ...Object.values(datafeedSetupResults) + .filter(hasError) + .map((datafeed) => datafeed.error.msg), + ...Object.values(jobSetupResults) + .filter(hasError) + .map((job) => job.error.msg), + ], + }; + + return { + ...state, + jobStatus: nextJobStatus, + jobSummaries, + setupStatus: nextSetupStatus, + }; + } + case 'failedSetup': { + return { + ...state, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'failed', + }), + {} as Record + ), + setupStatus: { type: 'failed', reasons: ['unknown'] }, + }; + } + case 'fetchingJobStatuses': { + return { + ...state, + setupStatus: + state.setupStatus.type === 'unknown' ? { type: 'initializing' } : state.setupStatus, + }; + } + case 'fetchedJobStatuses': { + const { payload: jobSummaries, spaceId, sourceId } = action; + const { setupStatus } = state; + const nextJobStatus = jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: getJobStatus(getJobId(spaceId, sourceId, jobType))(jobSummaries), + }), + {} as Record + ); + const nextSetupStatus = getSetupStatus(nextJobStatus)(setupStatus); + + return { + ...state, + jobSummaries, + jobStatus: nextJobStatus, + setupStatus: nextSetupStatus, + }; + } + case 'failedFetchingJobStatuses': { + return { + ...state, + setupStatus: { type: 'unknown' }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'unknown', + }), + {} as Record + ), + }; + } + case 'viewedResults': { + return { + ...state, + setupStatus: { type: 'skipped', newlyCreated: true }, + }; + } + default: { + return state; + } + } +}; + +const hasSuccessfullyCreatedJob = (jobId: string) => ( + jobSetupResponses: SetupMlModuleResponsePayload['jobs'] +) => + jobSetupResponses.filter( + (jobSetupResponse) => + jobSetupResponse.id === jobId && jobSetupResponse.success && !jobSetupResponse.error + ).length > 0; + +const hasSuccessfullyStartedDatafeed = (datafeedId: string) => ( + datafeedSetupResponses: SetupMlModuleResponsePayload['datafeeds'] +) => + datafeedSetupResponses.filter( + (datafeedSetupResponse) => + datafeedSetupResponse.id === datafeedId && + datafeedSetupResponse.success && + datafeedSetupResponse.started && + !datafeedSetupResponse.error + ).length > 0; + +const getJobStatus = (jobId: string) => ( + jobSummaries: FetchJobStatusResponsePayload +): JobStatus => { + return ( + jobSummaries + .filter((jobSummary) => jobSummary.id === jobId) + .map( + (jobSummary): JobStatus => { + if (jobSummary.jobState === 'failed' || jobSummary.datafeedState === '') { + return 'failed'; + } else if ( + jobSummary.jobState === 'closed' && + jobSummary.datafeedState === 'stopped' && + jobSummary.fullJob && + jobSummary.fullJob.finished_time != null + ) { + return 'finished'; + } else if ( + jobSummary.jobState === 'closed' || + jobSummary.jobState === 'closing' || + jobSummary.datafeedState === 'stopped' + ) { + return 'stopped'; + } else if (jobSummary.jobState === 'opening') { + return 'initializing'; + } else if (jobSummary.jobState === 'opened' && jobSummary.datafeedState === 'started') { + return 'started'; + } + + return 'unknown'; + } + )[0] || 'missing' + ); +}; + +const getSetupStatus = (everyJobStatus: Record) => ( + previousSetupStatus: SetupStatus +): SetupStatus => { + return Object.entries(everyJobStatus).reduce( + (setupStatus, [, jobStatus]) => { + if (jobStatus === 'missing') { + return { type: 'required' }; + } else if (setupStatus.type === 'required' || setupStatus.type === 'succeeded') { + return setupStatus; + } else if (setupStatus.type === 'skipped' || isJobStatusWithResults(jobStatus)) { + return { + type: 'skipped', + // preserve newlyCreated status + newlyCreated: setupStatus.type === 'skipped' && setupStatus.newlyCreated, + }; + } + + return setupStatus; + }, + previousSetupStatus + ); +}; + +const hasError = ( + value: Value +): value is MandatoryProperty => value.error != null; + +export const useModuleStatus = (jobTypes: JobType[]) => { + return useReducer(createStatusReducer(jobTypes), { jobTypes }, createInitialState); +}; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts new file mode 100644 index 0000000000000..a9f2671de8259 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + ValidateLogEntryDatasetsResponsePayload, + ValidationIndicesResponsePayload, +} from '../../../common/http_api/log_analysis'; +import { DatasetFilter } from '../../../common/infra_ml'; +import { DeleteJobsResponsePayload } from './api/ml_cleanup'; +import { FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api'; +import { GetMlModuleResponsePayload } from './api/ml_get_module'; +import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; + +export { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api'; + +export interface ModuleDescriptor { + moduleId: string; + moduleName: string; + moduleDescription: string; + jobTypes: JobType[]; + bucketSpan: number; + getJobIds: (spaceId: string, sourceId: string) => Record; + getJobSummary: (spaceId: string, sourceId: string) => Promise; + getModuleDefinition: () => Promise; + setUpModule: ( + start: number | undefined, + end: number | undefined, + datasetFilter: DatasetFilter, + sourceConfiguration: ModuleSourceConfiguration, + partitionField?: string + ) => Promise; + cleanUpModule: (spaceId: string, sourceId: string) => Promise; + validateSetupIndices: ( + indices: string[], + timestampField: string + ) => Promise; + validateSetupDatasets: ( + indices: string[], + timestampField: string, + startTime: number, + endTime: number + ) => Promise; +} + +export interface ModuleSourceConfiguration { + indices: string[]; + sourceId: string; + spaceId: string; + timestampField: string; +} + +interface ManyCategoriesWarningReason { + type: 'manyCategories'; + categoriesDocumentRatio: number; +} + +interface ManyDeadCategoriesWarningReason { + type: 'manyDeadCategories'; + deadCategoriesRatio: number; +} + +interface ManyRareCategoriesWarningReason { + type: 'manyRareCategories'; + rareCategoriesRatio: number; +} + +interface NoFrequentCategoriesWarningReason { + type: 'noFrequentCategories'; +} + +interface SingleCategoryWarningReason { + type: 'singleCategory'; +} + +export type CategoryQualityWarningReason = + | ManyCategoriesWarningReason + | ManyDeadCategoriesWarningReason + | ManyRareCategoriesWarningReason + | NoFrequentCategoriesWarningReason + | SingleCategoryWarningReason; + +export type CategoryQualityWarningReasonType = CategoryQualityWarningReason['type']; + +export interface CategoryQualityWarning { + type: 'categoryQualityWarning'; + jobId: string; + reasons: CategoryQualityWarningReason[]; +} + +export type QualityWarning = CategoryQualityWarning; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_setup_state.ts b/x-pack/plugins/infra/public/containers/ml/infra_ml_setup_state.ts new file mode 100644 index 0000000000000..0dfe3b301f240 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_setup_state.ts @@ -0,0 +1,289 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEqual } from 'lodash'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { usePrevious } from 'react-use'; +import { + combineDatasetFilters, + DatasetFilter, + filterDatasetFilter, + isExampleDataIndex, +} from '../../../common/infra_ml'; +import { + AvailableIndex, + ValidationIndicesError, + ValidationUIError, +} from '../../components/logging/log_analysis_setup/initial_configuration_step'; +import { useTrackedPromise } from '../../utils/use_tracked_promise'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './infra_ml_module_types'; + +type SetupHandler = ( + indices: string[], + startTime: number | undefined, + endTime: number | undefined, + datasetFilter: DatasetFilter +) => void; + +interface AnalysisSetupStateArguments { + cleanUpAndSetUpModule: SetupHandler; + moduleDescriptor: ModuleDescriptor; + setUpModule: SetupHandler; + sourceConfiguration: ModuleSourceConfiguration; +} + +const fourWeeksInMs = 86400000 * 7 * 4; + +export const useAnalysisSetupState = ({ + cleanUpAndSetUpModule, + moduleDescriptor: { validateSetupDatasets, validateSetupIndices }, + setUpModule, + sourceConfiguration, +}: AnalysisSetupStateArguments) => { + const [startTime, setStartTime] = useState(Date.now() - fourWeeksInMs); + const [endTime, setEndTime] = useState(undefined); + + const isTimeRangeValid = useMemo( + () => (startTime != null && endTime != null ? startTime < endTime : true), + [endTime, startTime] + ); + + const [validatedIndices, setValidatedIndices] = useState( + sourceConfiguration.indices.map((indexName) => ({ + name: indexName, + validity: 'unknown' as const, + })) + ); + + const updateIndicesWithValidationErrors = useCallback( + (validationErrors: ValidationIndicesError[]) => + setValidatedIndices((availableIndices) => + availableIndices.map((previousAvailableIndex) => { + const indexValiationErrors = validationErrors.filter( + ({ index }) => index === previousAvailableIndex.name + ); + + if (indexValiationErrors.length > 0) { + return { + validity: 'invalid', + name: previousAvailableIndex.name, + errors: indexValiationErrors, + }; + } else if (previousAvailableIndex.validity === 'valid') { + return { + ...previousAvailableIndex, + validity: 'valid', + errors: [], + }; + } else { + return { + validity: 'valid', + name: previousAvailableIndex.name, + isSelected: !isExampleDataIndex(previousAvailableIndex.name), + availableDatasets: [], + datasetFilter: { + type: 'includeAll' as const, + }, + }; + } + }) + ), + [] + ); + + const updateIndicesWithAvailableDatasets = useCallback( + (availableDatasets: Array<{ indexName: string; datasets: string[] }>) => + setValidatedIndices((availableIndices) => + availableIndices.map((previousAvailableIndex) => { + if (previousAvailableIndex.validity !== 'valid') { + return previousAvailableIndex; + } + + const availableDatasetsForIndex = availableDatasets.filter( + ({ indexName }) => indexName === previousAvailableIndex.name + ); + const newAvailableDatasets = availableDatasetsForIndex.flatMap( + ({ datasets }) => datasets + ); + + // filter out datasets that have disappeared if this index' datasets were updated + const newDatasetFilter: DatasetFilter = + availableDatasetsForIndex.length > 0 + ? filterDatasetFilter(previousAvailableIndex.datasetFilter, (dataset) => + newAvailableDatasets.includes(dataset) + ) + : previousAvailableIndex.datasetFilter; + + return { + ...previousAvailableIndex, + availableDatasets: newAvailableDatasets, + datasetFilter: newDatasetFilter, + }; + }) + ), + [] + ); + + const validIndexNames = useMemo( + () => validatedIndices.filter((index) => index.validity === 'valid').map((index) => index.name), + [validatedIndices] + ); + + const selectedIndexNames = useMemo( + () => + validatedIndices + .filter((index) => index.validity === 'valid' && index.isSelected) + .map((i) => i.name), + [validatedIndices] + ); + + const datasetFilter = useMemo( + () => + validatedIndices + .flatMap((validatedIndex) => + validatedIndex.validity === 'valid' + ? validatedIndex.datasetFilter + : { type: 'includeAll' as const } + ) + .reduce(combineDatasetFilters, { type: 'includeAll' as const }), + [validatedIndices] + ); + + const [validateIndicesRequest, validateIndices] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await validateSetupIndices( + sourceConfiguration.indices, + sourceConfiguration.timestampField + ); + }, + onResolve: ({ data: { errors } }) => { + updateIndicesWithValidationErrors(errors); + }, + onReject: () => { + setValidatedIndices([]); + }, + }, + [sourceConfiguration.indices, sourceConfiguration.timestampField] + ); + + const [validateDatasetsRequest, validateDatasets] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + if (validIndexNames.length === 0) { + return { data: { datasets: [] } }; + } + + return await validateSetupDatasets( + validIndexNames, + sourceConfiguration.timestampField, + startTime ?? 0, + endTime ?? Date.now() + ); + }, + onResolve: ({ data: { datasets } }) => { + updateIndicesWithAvailableDatasets(datasets); + }, + }, + [validIndexNames, sourceConfiguration.timestampField, startTime, endTime] + ); + + const setUp = useCallback(() => { + return setUpModule(selectedIndexNames, startTime, endTime, datasetFilter); + }, [setUpModule, selectedIndexNames, startTime, endTime, datasetFilter]); + + const cleanUpAndSetUp = useCallback(() => { + return cleanUpAndSetUpModule(selectedIndexNames, startTime, endTime, datasetFilter); + }, [cleanUpAndSetUpModule, selectedIndexNames, startTime, endTime, datasetFilter]); + + const isValidating = useMemo( + () => validateIndicesRequest.state === 'pending' || validateDatasetsRequest.state === 'pending', + [validateDatasetsRequest.state, validateIndicesRequest.state] + ); + + const validationErrors = useMemo(() => { + if (isValidating) { + return []; + } + + return [ + // validate request status + ...(validateIndicesRequest.state === 'rejected' || + validateDatasetsRequest.state === 'rejected' + ? [{ error: 'NETWORK_ERROR' as const }] + : []), + // validation request results + ...validatedIndices.reduce((errors, index) => { + return index.validity === 'invalid' && selectedIndexNames.includes(index.name) + ? [...errors, ...index.errors] + : errors; + }, []), + // index count + ...(selectedIndexNames.length === 0 ? [{ error: 'TOO_FEW_SELECTED_INDICES' as const }] : []), + // time range + ...(!isTimeRangeValid ? [{ error: 'INVALID_TIME_RANGE' as const }] : []), + ]; + }, [ + isValidating, + validateIndicesRequest.state, + validateDatasetsRequest.state, + validatedIndices, + selectedIndexNames, + isTimeRangeValid, + ]); + + const prevStartTime = usePrevious(startTime); + const prevEndTime = usePrevious(endTime); + const prevValidIndexNames = usePrevious(validIndexNames); + + useEffect(() => { + if (!isTimeRangeValid) { + return; + } + + validateIndices(); + }, [isTimeRangeValid, validateIndices]); + + useEffect(() => { + if (!isTimeRangeValid) { + return; + } + + if ( + startTime !== prevStartTime || + endTime !== prevEndTime || + !isEqual(validIndexNames, prevValidIndexNames) + ) { + validateDatasets(); + } + }, [ + endTime, + isTimeRangeValid, + prevEndTime, + prevStartTime, + prevValidIndexNames, + startTime, + validIndexNames, + validateDatasets, + ]); + + return { + cleanUpAndSetUp, + datasetFilter, + endTime, + isValidating, + selectedIndexNames, + setEndTime, + setStartTime, + setUp, + startTime, + validatedIndices, + setValidatedIndices, + validationErrors, + }; +}; diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx new file mode 100644 index 0000000000000..9c065f3e91bc4 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import createContainer from 'constate'; +import { useMemo } from 'react'; +import { useInfraMLModule } from '../../infra_ml_module'; +import { useInfraMLModuleConfiguration } from '../../infra_ml_module_configuration'; +import { useInfraMLModuleDefinition } from '../../infra_ml_module_definition'; +import { ModuleSourceConfiguration } from '../../infra_ml_module_types'; +import { metricHostsModule } from './module_descriptor'; + +export const useMetricHostsModule = ({ + indexPattern, + sourceId, + spaceId, + timestampField, +}: { + indexPattern: string; + sourceId: string; + spaceId: string; + timestampField: string; +}) => { + const sourceConfiguration: ModuleSourceConfiguration = useMemo( + () => ({ + indices: indexPattern.split(','), + sourceId, + spaceId, + timestampField, + }), + [indexPattern, sourceId, spaceId, timestampField] + ); + + const infraMLModule = useInfraMLModule({ + moduleDescriptor: metricHostsModule, + sourceConfiguration, + }); + + const { getIsJobConfigurationOutdated } = useInfraMLModuleConfiguration({ + sourceConfiguration, + moduleDescriptor: metricHostsModule, + }); + + const { fetchModuleDefinition, getIsJobDefinitionOutdated } = useInfraMLModuleDefinition({ + sourceConfiguration, + moduleDescriptor: metricHostsModule, + }); + + const hasOutdatedJobConfigurations = useMemo( + () => infraMLModule.jobSummaries.some(getIsJobConfigurationOutdated), + [getIsJobConfigurationOutdated, infraMLModule.jobSummaries] + ); + + const hasOutdatedJobDefinitions = useMemo( + () => infraMLModule.jobSummaries.some(getIsJobDefinitionOutdated), + [getIsJobDefinitionOutdated, infraMLModule.jobSummaries] + ); + + const hasStoppedJobs = useMemo( + () => + Object.values(infraMLModule.jobStatus).some( + (currentJobStatus) => currentJobStatus === 'stopped' + ), + [infraMLModule.jobStatus] + ); + + return { + ...infraMLModule, + fetchModuleDefinition, + hasOutdatedJobConfigurations, + hasOutdatedJobDefinitions, + hasStoppedJobs, + }; +}; + +export const [MetricHostsModuleProvider, useMetricHostsModuleContext] = createContainer( + useMetricHostsModule +); diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts new file mode 100644 index 0000000000000..cec87fb1144e3 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { ModuleDescriptor, ModuleSourceConfiguration } from '../../infra_ml_module_types'; +import { cleanUpJobsAndDatafeeds } from '../../infra_ml_cleanup'; +import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api'; +import { callGetMlModuleAPI } from '../../api/ml_get_module'; +import { callSetupMlModuleAPI } from '../../api/ml_setup_module_api'; +import { callValidateIndicesAPI } from '../../../logs/log_analysis/api/validate_indices'; +import { callValidateDatasetsAPI } from '../../../logs/log_analysis/api/validate_datasets'; +import { + metricsHostsJobTypes, + getJobId, + MetricsHostsJobType, + DatasetFilter, + bucketSpan, + partitionField, +} from '../../../../../common/infra_ml'; + +const moduleId = 'metrics_ui_hosts'; +const moduleName = i18n.translate('xpack.infra.ml.metricsModuleName', { + defaultMessage: 'Metrics anomanly detection', +}); +const moduleDescription = i18n.translate('xpack.infra.ml.metricsHostModuleDescription', { + defaultMessage: 'Use Machine Learning to automatically detect anomalous log entry rates.', +}); + +const getJobIds = (spaceId: string, sourceId: string) => + metricsHostsJobTypes.reduce( + (accumulatedJobIds, jobType) => ({ + ...accumulatedJobIds, + [jobType]: getJobId(spaceId, sourceId, jobType), + }), + {} as Record + ); + +const getJobSummary = async (spaceId: string, sourceId: string) => { + const response = await callJobsSummaryAPI(spaceId, sourceId, metricsHostsJobTypes); + const jobIds = Object.values(getJobIds(spaceId, sourceId)); + + return response.filter((jobSummary) => jobIds.includes(jobSummary.id)); +}; + +const getModuleDefinition = async () => { + return await callGetMlModuleAPI(moduleId); +}; + +const setUpModule = async ( + start: number | undefined, + end: number | undefined, + datasetFilter: DatasetFilter, + { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration, + pField?: string +) => { + const indexNamePattern = indices.join(','); + const jobIds = ['hosts_memory_usage', 'hosts_network_in', 'hosts_network_out']; + const jobOverrides = jobIds.map((id) => ({ + job_id: id, + data_description: { + time_field: timestampField, + }, + custom_settings: { + metrics_source_config: { + indexPattern: indexNamePattern, + timestampField, + bucketSpan, + }, + }, + })); + + return callSetupMlModuleAPI( + moduleId, + start, + end, + spaceId, + sourceId, + indexNamePattern, + jobOverrides, + [] + ); +}; + +const cleanUpModule = async (spaceId: string, sourceId: string) => { + return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsHostsJobTypes); +}; + +const validateSetupIndices = async (indices: string[], timestampField: string) => { + return await callValidateIndicesAPI(indices, [ + { + name: timestampField, + validTypes: ['date'], + }, + { + name: partitionField, + validTypes: ['keyword'], + }, + ]); +}; + +const validateSetupDatasets = async ( + indices: string[], + timestampField: string, + startTime: number, + endTime: number +) => { + return await callValidateDatasetsAPI(indices, timestampField, startTime, endTime); +}; + +export const metricHostsModule: ModuleDescriptor = { + moduleId, + moduleName, + moduleDescription, + jobTypes: metricsHostsJobTypes, + bucketSpan, + getJobIds, + getJobSummary, + getModuleDefinition, + setUpModule, + cleanUpModule, + validateSetupDatasets, + validateSetupIndices, +}; diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx new file mode 100644 index 0000000000000..07c8ab02f17ee --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import createContainer from 'constate'; +import { useMemo } from 'react'; +import { useInfraMLModule } from '../../infra_ml_module'; +import { useInfraMLModuleConfiguration } from '../../infra_ml_module_configuration'; +import { useInfraMLModuleDefinition } from '../../infra_ml_module_definition'; +import { ModuleSourceConfiguration } from '../../infra_ml_module_types'; +import { metricHostsModule } from './module_descriptor'; + +export const useMetricK8sModule = ({ + indexPattern, + sourceId, + spaceId, + timestampField, +}: { + indexPattern: string; + sourceId: string; + spaceId: string; + timestampField: string; +}) => { + const sourceConfiguration: ModuleSourceConfiguration = useMemo( + () => ({ + indices: indexPattern.split(','), + sourceId, + spaceId, + timestampField, + }), + [indexPattern, sourceId, spaceId, timestampField] + ); + + const infraMLModule = useInfraMLModule({ + moduleDescriptor: metricHostsModule, + sourceConfiguration, + }); + + const { getIsJobConfigurationOutdated } = useInfraMLModuleConfiguration({ + sourceConfiguration, + moduleDescriptor: metricHostsModule, + }); + + const { fetchModuleDefinition, getIsJobDefinitionOutdated } = useInfraMLModuleDefinition({ + sourceConfiguration, + moduleDescriptor: metricHostsModule, + }); + + const hasOutdatedJobConfigurations = useMemo( + () => infraMLModule.jobSummaries.some(getIsJobConfigurationOutdated), + [getIsJobConfigurationOutdated, infraMLModule.jobSummaries] + ); + + const hasOutdatedJobDefinitions = useMemo( + () => infraMLModule.jobSummaries.some(getIsJobDefinitionOutdated), + [getIsJobDefinitionOutdated, infraMLModule.jobSummaries] + ); + + const hasStoppedJobs = useMemo( + () => + Object.values(infraMLModule.jobStatus).some( + (currentJobStatus) => currentJobStatus === 'stopped' + ), + [infraMLModule.jobStatus] + ); + + return { + ...infraMLModule, + fetchModuleDefinition, + hasOutdatedJobConfigurations, + hasOutdatedJobDefinitions, + hasStoppedJobs, + }; +}; + +export const [MetricK8sModuleProvider, useMetricK8sModuleContext] = createContainer( + useMetricK8sModule +); diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts new file mode 100644 index 0000000000000..cbcff1c307af6 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { ModuleDescriptor, ModuleSourceConfiguration } from '../../infra_ml_module_types'; +import { cleanUpJobsAndDatafeeds } from '../../infra_ml_cleanup'; +import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api'; +import { callGetMlModuleAPI } from '../../api/ml_get_module'; +import { callSetupMlModuleAPI } from '../../api/ml_setup_module_api'; +import { callValidateIndicesAPI } from '../../../logs/log_analysis/api/validate_indices'; +import { callValidateDatasetsAPI } from '../../../logs/log_analysis/api/validate_datasets'; +import { + metricsK8SJobTypes, + getJobId, + MetricK8sJobType, + DatasetFilter, + bucketSpan, + partitionField, +} from '../../../../../common/infra_ml'; + +const moduleId = 'metrics_ui_k8s'; +const moduleName = i18n.translate('xpack.infra.ml.metricsModuleName', { + defaultMessage: 'Metrics anomanly detection', +}); +const moduleDescription = i18n.translate('xpack.infra.ml.metricsHostModuleDescription', { + defaultMessage: 'Use Machine Learning to automatically detect anomalous log entry rates.', +}); + +const getJobIds = (spaceId: string, sourceId: string) => + metricsK8SJobTypes.reduce( + (accumulatedJobIds, jobType) => ({ + ...accumulatedJobIds, + [jobType]: getJobId(spaceId, sourceId, jobType), + }), + {} as Record + ); + +const getJobSummary = async (spaceId: string, sourceId: string) => { + const response = await callJobsSummaryAPI(spaceId, sourceId, metricsK8SJobTypes); + const jobIds = Object.values(getJobIds(spaceId, sourceId)); + + return response.filter((jobSummary) => jobIds.includes(jobSummary.id)); +}; + +const getModuleDefinition = async () => { + return await callGetMlModuleAPI(moduleId); +}; + +const setUpModule = async ( + start: number | undefined, + end: number | undefined, + datasetFilter: DatasetFilter, + { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration, + pField?: string +) => { + const indexNamePattern = indices.join(','); + const jobIds = ['k8s_memory_usage', 'k8s_network_in', 'k8s_network_out']; + const jobOverrides = jobIds.map((id) => ({ + job_id: id, + analysis_config: { + bucket_span: `${bucketSpan}ms`, + }, + data_description: { + time_field: timestampField, + }, + custom_settings: { + metrics_source_config: { + indexPattern: indexNamePattern, + timestampField, + bucketSpan, + }, + }, + })); + + return callSetupMlModuleAPI( + moduleId, + start, + end, + spaceId, + sourceId, + indexNamePattern, + jobOverrides, + [] + ); +}; + +const cleanUpModule = async (spaceId: string, sourceId: string) => { + return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsK8SJobTypes); +}; + +const validateSetupIndices = async (indices: string[], timestampField: string) => { + return await callValidateIndicesAPI(indices, [ + { + name: timestampField, + validTypes: ['date'], + }, + { + name: partitionField, + validTypes: ['keyword'], + }, + ]); +}; + +const validateSetupDatasets = async ( + indices: string[], + timestampField: string, + startTime: number, + endTime: number +) => { + return await callValidateDatasetsAPI(indices, timestampField, startTime, endTime); +}; + +export const metricHostsModule: ModuleDescriptor = { + moduleId, + moduleName, + moduleDescription, + jobTypes: metricsK8SJobTypes, + bucketSpan, + getJobIds, + getJobSummary, + getModuleDefinition, + setUpModule, + cleanUpModule, + validateSetupDatasets, + validateSetupIndices, +}; diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx index 0556955e47f66..e1b294c8383e3 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx @@ -19,7 +19,7 @@ describe('RedirectToLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); @@ -33,7 +33,7 @@ describe('RedirectToLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx index a32e1e68141b5..0541b811508ee 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx @@ -5,6 +5,8 @@ */ import { i18n } from '@kbn/i18n'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import flowRight from 'lodash/flowRight'; import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx index 2880b1b794443..b5765942e9f10 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; import { @@ -14,6 +14,10 @@ import { MissingSetupPrivilegesPrompt, SubscriptionSplashContent, } from '../../../components/logging/log_analysis_setup'; +import { + LogAnalysisSetupFlyout, + useLogAnalysisSetupFlyoutStateContext, +} from '../../../components/logging/log_analysis_setup/setup_flyout'; import { SourceErrorPage } from '../../../components/source_error_page'; import { SourceLoadingPage } from '../../../components/source_loading_page'; import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; @@ -21,7 +25,6 @@ import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log import { useLogSourceContext } from '../../../containers/logs/log_source'; import { LogEntryCategoriesResultsContent } from './page_results_content'; import { LogEntryCategoriesSetupContent } from './page_setup_content'; -import { LogEntryCategoriesSetupFlyout } from './setup_flyout'; export const LogEntryCategoriesPageContent = () => { const { @@ -40,9 +43,10 @@ export const LogEntryCategoriesPageContent = () => { const { fetchJobStatus, setupStatus, jobStatus } = useLogEntryCategoriesModuleContext(); - const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); - const openFlyout = useCallback(() => setIsFlyoutOpen(true), []); - const closeFlyout = useCallback(() => setIsFlyoutOpen(false), []); + const { showModuleSetup } = useLogAnalysisSetupFlyoutStateContext(); + const showCategoriesModuleSetup = useCallback(() => showModuleSetup('logs_ui_categories'), [ + showModuleSetup, + ]); useEffect(() => { if (hasLogAnalysisReadCapabilities) { @@ -71,8 +75,8 @@ export const LogEntryCategoriesPageContent = () => { } else if (isJobStatusWithResults(jobStatus['log-entry-categories-count'])) { return ( <> - - + + ); } else if (!hasLogAnalysisSetupCapabilities) { @@ -80,9 +84,11 @@ export const LogEntryCategoriesPageContent = () => { } else { return ( <> - - + + ); } }; + +const allowedSetupModules = ['logs_ui_categories' as const]; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_providers.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_providers.tsx index 723d833799e29..7d2f1d5418bc5 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_providers.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_providers.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; +import { LogAnalysisSetupFlyoutStateProvider } from '../../../components/logging/log_analysis_setup/setup_flyout'; import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_categories'; import { useLogSourceContext } from '../../../containers/logs/log_source'; import { useActiveKibanaSpace } from '../../../hooks/use_kibana_space'; @@ -27,7 +28,7 @@ export const LogEntryCategoriesPageProviders: React.FunctionComponent = ({ child spaceId={space.id} timestampField={sourceConfiguration.configuration.fields.timestamp} > - {children} + {children} ); }; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/setup_flyout.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/setup_flyout.tsx deleted file mode 100644 index a038765de2bf3..0000000000000 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/setup_flyout.tsx +++ /dev/null @@ -1,128 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutHeader, - EuiSpacer, - EuiSteps, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useCallback, useMemo } from 'react'; -import { - createInitialConfigurationStep, - createProcessStep, -} from '../../../components/logging/log_analysis_setup'; -import { useLogEntryCategoriesSetup } from '../../../containers/logs/log_analysis/modules/log_entry_categories'; - -interface LogEntryCategoriesSetupFlyoutProps { - isOpen: boolean; - onClose: () => void; -} - -export const LogEntryCategoriesSetupFlyout: React.FC = ({ - isOpen, - onClose, -}) => { - const { - cleanUpAndSetUp, - endTime, - isValidating, - lastSetupErrorMessages, - setEndTime, - setStartTime, - setValidatedIndices, - setUp, - setupStatus, - startTime, - validatedIndices, - validationErrors, - viewResults, - } = useLogEntryCategoriesSetup(); - - const viewResultsAndClose = useCallback(() => { - viewResults(); - onClose(); - }, [viewResults, onClose]); - - const steps = useMemo( - () => [ - createInitialConfigurationStep({ - setStartTime, - setEndTime, - startTime, - endTime, - isValidating, - validatedIndices, - setupStatus, - setValidatedIndices, - validationErrors, - }), - createProcessStep({ - cleanUpAndSetUp, - errorMessages: lastSetupErrorMessages, - isConfigurationValid: validationErrors.length <= 0 && !isValidating, - setUp, - setupStatus, - viewResults: viewResultsAndClose, - }), - ], - [ - cleanUpAndSetUp, - endTime, - isValidating, - lastSetupErrorMessages, - setEndTime, - setStartTime, - setUp, - setValidatedIndices, - setupStatus, - startTime, - validatedIndices, - validationErrors, - viewResultsAndClose, - ] - ); - - if (!isOpen) { - return null; - } - return ( - - - -

- -

-
-
- - -

- -

-
- - - - - -
-
- ); -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 3b3ed80f9e731..ac2c87248ae77 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -38,6 +38,8 @@ import { MetricsAlertDropdown } from '../../alerting/metric_threshold/components import { SavedView } from '../../containers/saved_view/saved_view'; import { SourceConfigurationFields } from '../../graphql/types'; import { AlertPrefillProvider } from '../../alerting/use_alert_prefill'; +import { InfraMLCapabilitiesProvider } from '../../containers/ml/infra_ml_capabilities'; +import { AnomalyDetectionFlyout } from './inventory_view/components/ml/anomaly_detection/anomoly_detection_flyout'; const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', { defaultMessage: 'Add data', @@ -55,110 +57,118 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { - - + + + - + -
+ - - - - - - - - - - - - {ADD_DATA_LABEL} - - - - - - - - ( - - {({ configuration, createDerivedIndexPattern }) => ( - - - {configuration ? ( - - ) : ( - - )} - - )} - + } )} - /> - - - + > + + + + + + + + + + + + + + {ADD_DATA_LABEL} + + + + + + + + ( + + {({ configuration, createDerivedIndexPattern }) => ( + + + {configuration ? ( + + ) : ( + + )} + + )} + + )} + /> + + + + diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx new file mode 100644 index 0000000000000..9cb84c7fff438 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; + +import { euiStyled, useUiTracker } from '../../../../../../observability/public'; +import { InfraFormatter } from '../../../../lib/lib'; +import { Timeline } from './timeline/timeline'; + +const showHistory = i18n.translate('xpack.infra.showHistory', { + defaultMessage: 'Show history', +}); +const hideHistory = i18n.translate('xpack.infra.hideHistory', { + defaultMessage: 'Hide history', +}); + +const TRANSITION_MS = 300; + +export const BottomDrawer: React.FC<{ + measureRef: (instance: HTMLElement | null) => void; + interval: string; + formatter: InfraFormatter; +}> = ({ measureRef, interval, formatter, children }) => { + const [isOpen, setIsOpen] = useState(false); + + const trackDrawerOpen = useUiTracker({ app: 'infra_metrics' }); + const onClick = useCallback(() => { + if (!isOpen) trackDrawerOpen({ metric: 'open_timeline_drawer__inventory' }); + setIsOpen(!isOpen); + }, [isOpen, trackDrawerOpen]); + + return ( + + + + + {isOpen ? hideHistory : showHistory} + + + + {children} + + + + + + + + + + ); +}; + +const BottomActionContainer = euiStyled.div<{ isOpen: boolean }>` + padding: ${(props) => props.theme.eui.paddingSizes.m} 0; + position: fixed; + left: 0; + bottom: 0; + right: 0; + transition: transform ${TRANSITION_MS}ms; + transform: translateY(${(props) => (props.isOpen ? 0 : '224px')}) +`; + +const BottomActionTopBar = euiStyled(EuiFlexGroup).attrs({ + justifyContent: 'spaceBetween', + alignItems: 'center', +})` + margin-bottom: 0; + height: 48px; +`; + +const ShowHideButton = euiStyled(EuiButtonEmpty).attrs({ size: 's' })` + width: 140px; +`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 47616c7f4f7fd..712578be7dffd 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect } from 'react'; import { useInterval } from 'react-use'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { AutoSizer } from '../../../../components/auto_sizer'; import { convertIntervalToString } from '../../../../utils/convert_interval_to_string'; import { NodesOverview } from './nodes_overview'; @@ -23,12 +23,13 @@ import { euiStyled } from '../../../../../../observability/public'; import { Toolbar } from './toolbars/toolbar'; import { ViewSwitcher } from './waffle/view_switcher'; import { IntervalLabel } from './waffle/interval_label'; -import { Legend } from './waffle/legend'; import { createInventoryMetricFormatter } from '../lib/create_inventory_metric_formatter'; import { createLegend } from '../lib/create_legend'; import { useSavedViewContext } from '../../../../containers/saved_view/saved_view'; import { useWaffleViewState } from '../hooks/use_waffle_view_state'; import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control'; +import { BottomDrawer } from './bottom_drawer'; +import { Legend } from './waffle/legend'; export const Layout = () => { const { sourceId, source } = useSourceContext(); @@ -104,12 +105,19 @@ export const Layout = () => { - + + + + + + + + {({ measureRef, bounds: { height = 0 } }) => ( @@ -128,24 +136,14 @@ export const Layout = () => { formatter={formatter} bottomMargin={height} /> - - - - - - - - - - - - - + + + )} @@ -164,12 +162,8 @@ const TopActionContainer = euiStyled.div` padding: ${(props) => `12px ${props.theme.eui.paddingSizes.m}`}; `; -const BottomActionContainer = euiStyled.div` - background-color: ${(props) => props.theme.eui.euiPageBackgroundColor}; - padding: ${(props) => props.theme.eui.paddingSizes.m} ${(props) => - props.theme.eui.paddingSizes.m}; - position: fixed; - left: 0; - bottom: 0; - right: 0; +const SavedViewContainer = euiStyled.div` + position: relative; + z-index: 1; + padding-left: ${(props) => props.theme.eui.paddingSizes.m}; `; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomoly_detection_flyout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomoly_detection_flyout.tsx new file mode 100644 index 0000000000000..b063713fa2c97 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomoly_detection_flyout.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useCallback } from 'react'; +import { EuiButtonEmpty, EuiFlyout } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { FlyoutHome } from './flyout_home'; +import { JobSetupScreen } from './job_setup_screen'; +import { useInfraMLCapabilities } from '../../../../../../containers/ml/infra_ml_capabilities'; +import { MetricHostsModuleProvider } from '../../../../../../containers/ml/modules/metrics_hosts/module'; +import { MetricK8sModuleProvider } from '../../../../../../containers/ml/modules/metrics_k8s/module'; +import { useSourceViaHttp } from '../../../../../../containers/source/use_source_via_http'; +import { useActiveKibanaSpace } from '../../../../../../hooks/use_kibana_space'; + +export const AnomalyDetectionFlyout = () => { + const { hasInfraMLSetupCapabilities } = useInfraMLCapabilities(); + const [showFlyout, setShowFlyout] = useState(false); + const [screenName, setScreenName] = useState<'home' | 'setup'>('home'); + const [screenParams, setScreenParams] = useState(null); + const { source } = useSourceViaHttp({ + sourceId: 'default', + type: 'metrics', + }); + + const { space } = useActiveKibanaSpace(); + + const openFlyout = useCallback(() => { + setScreenName('home'); + setShowFlyout(true); + }, []); + + const openJobSetup = useCallback( + (jobType: 'hosts' | 'kubernetes') => { + setScreenName('setup'); + setScreenParams({ jobType }); + }, + [setScreenName] + ); + + const closeFlyout = useCallback(() => { + setShowFlyout(false); + }, []); + + if (source?.configuration.metricAlias == null || space == null) { + return null; + } + + return ( + <> + + + + {showFlyout && ( + + + + {screenName === 'home' && ( + + )} + {screenName === 'setup' && ( + + )} + + + + )} + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx new file mode 100644 index 0000000000000..9cf898b684336 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx @@ -0,0 +1,333 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useCallback, useEffect } from 'react'; +import { EuiFlyoutHeader, EuiTitle, EuiFlyoutBody, EuiTabs, EuiTab, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiText, EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; +import { EuiButtonEmpty } from '@elastic/eui'; +import moment from 'moment'; +import { useInfraMLCapabilitiesContext } from '../../../../../../containers/ml/infra_ml_capabilities'; +import { SubscriptionSplashContent } from './subscription_splash_content'; +import { + MissingResultsPrivilegesPrompt, + MissingSetupPrivilegesPrompt, +} from '../../../../../../components/logging/log_analysis_setup'; +import { useMetricHostsModuleContext } from '../../../../../../containers/ml/modules/metrics_hosts/module'; +import { useMetricK8sModuleContext } from '../../../../../../containers/ml/modules/metrics_k8s/module'; +import { LoadingPage } from '../../../../../../components/loading_page'; +import { useLinkProps } from '../../../../../../hooks/use_link_props'; + +interface Props { + hasSetupCapabilities: boolean; + goToSetup(type: 'hosts' | 'kubernetes'): void; +} + +export const FlyoutHome = (props: Props) => { + const [tab, setTab] = useState<'jobs' | 'anomalies'>('jobs'); + const { goToSetup } = props; + const { + fetchJobStatus: fetchHostJobStatus, + setupStatus: hostSetupStatus, + jobSummaries: hostJobSummaries, + } = useMetricHostsModuleContext(); + const { + fetchJobStatus: fetchK8sJobStatus, + setupStatus: k8sSetupStatus, + jobSummaries: k8sJobSummaries, + } = useMetricK8sModuleContext(); + const { + hasInfraMLCapabilites, + hasInfraMLReadCapabilities, + hasInfraMLSetupCapabilities, + } = useInfraMLCapabilitiesContext(); + + const createHosts = useCallback(() => { + goToSetup('hosts'); + }, [goToSetup]); + + const createK8s = useCallback(() => { + goToSetup('kubernetes'); + }, [goToSetup]); + + const goToJobs = useCallback(() => { + setTab('jobs'); + }, []); + + const jobIds = [ + ...(k8sJobSummaries || []).map((k) => k.id), + ...(hostJobSummaries || []).map((h) => h.id), + ]; + const anomaliesUrl = useLinkProps({ + app: 'ml', + pathname: `/explorer?_g=${createResultsUrl(jobIds)}`, + }); + + useEffect(() => { + if (hasInfraMLReadCapabilities) { + fetchHostJobStatus(); + fetchK8sJobStatus(); + } + }, [fetchK8sJobStatus, fetchHostJobStatus, hasInfraMLReadCapabilities]); + + if (!hasInfraMLCapabilites) { + return ; + } else if (!hasInfraMLReadCapabilities) { + return ; + } else if (hostSetupStatus.type === 'initializing' || k8sSetupStatus.type === 'initializing') { + return ( + + ); + } else if (!hasInfraMLSetupCapabilities) { + return ; + } else { + return ( + <> + + +

+ +

+
+
+ + + + + + + + + + + + {hostJobSummaries.length > 0 && ( + <> + 0} + hasK8sJobs={k8sJobSummaries.length > 0} + /> + + + )} + {tab === 'jobs' && ( + 0} + hasK8sJobs={k8sJobSummaries.length > 0} + hasSetupCapabilities={props.hasSetupCapabilities} + createHosts={createHosts} + createK8s={createK8s} + /> + )} + + + ); + } +}; + +interface CalloutProps { + hasHostJobs: boolean; + hasK8sJobs: boolean; +} +const JobsEnabledCallout = (props: CalloutProps) => { + let target = ''; + if (props.hasHostJobs && props.hasK8sJobs) { + target = `${i18n.translate('xpack.infra.ml.anomalyFlyout.create.hostTitle', { + defaultMessage: 'Hosts', + })} and ${i18n.translate('xpack.infra.ml.anomalyFlyout.create.k8sSuccessTitle', { + defaultMessage: 'Kubernetes', + })}`; + } else if (props.hasHostJobs) { + target = i18n.translate('xpack.infra.ml.anomalyFlyout.create.hostSuccessTitle', { + defaultMessage: 'Hosts', + }); + } else if (props.hasK8sJobs) { + target = i18n.translate('xpack.infra.ml.anomalyFlyout.create.k8sSuccessTitle', { + defaultMessage: 'Kubernetes', + }); + } + + const manageJobsLinkProps = useLinkProps({ + app: 'ml', + pathname: '/jobs', + }); + + return ( + <> + + } + iconType="check" + /> + + + + + + ); +}; + +interface CreateJobTab { + hasSetupCapabilities: boolean; + hasHostJobs: boolean; + hasK8sJobs: boolean; + createHosts(): void; + createK8s(): void; +} + +const CreateJobTab = (props: CreateJobTab) => { + return ( + <> +
+ +

+ +

+
+ +

+ +

+
+
+ + + + + } + // title="Hosts" + title={ + + } + description={ + + } + footer={ + <> + {props.hasHostJobs && ( + + + + )} + {!props.hasHostJobs && ( + + + + )} + + } + /> + + + } + title={ + + } + description={ + + } + footer={ + <> + {props.hasK8sJobs && ( + + + + )} + {!props.hasK8sJobs && ( + + + + )} + + } + /> + + + + ); +}; + +function createResultsUrl(jobIds: string[], mode = 'absolute') { + const idString = jobIds.map((j) => `'${j}'`).join(','); + let path = ''; + + const from = moment().subtract(4, 'weeks').toISOString(); + const to = moment().toISOString(); + + path += `(ml:(jobIds:!(${idString}))`; + path += `,refreshInterval:(display:Off,pause:!f,value:0),time:(from:'${from}'`; + path += `,to:'${to}'`; + if (mode === 'invalid') { + path += `,mode:invalid`; + } + path += "))&_a=(query:(query_string:(analyze_wildcard:!t,query:'*')))"; + + return path; +} diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx new file mode 100644 index 0000000000000..730cd7b6e9ef5 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx @@ -0,0 +1,277 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useCallback, useMemo, useEffect } from 'react'; +import { EuiForm, EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; +import { EuiText, EuiSpacer } from '@elastic/eui'; +import { EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlyoutFooter } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; +import moment, { Moment } from 'moment'; +import { EuiComboBox } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { useSourceViaHttp } from '../../../../../../containers/source/use_source_via_http'; +import { useMetricK8sModuleContext } from '../../../../../../containers/ml/modules/metrics_k8s/module'; +import { useMetricHostsModuleContext } from '../../../../../../containers/ml/modules/metrics_hosts/module'; +import { FixedDatePicker } from '../../../../../../components/fixed_datepicker'; + +interface Props { + jobType: 'hosts' | 'kubernetes'; + closeFlyout(): void; + goHome(): void; +} + +export const JobSetupScreen = (props: Props) => { + const [now] = useState(() => moment()); + const { goHome } = props; + const [startDate, setStartDate] = useState(now.clone().subtract(4, 'weeks')); + const [partitionField, setPartitionField] = useState(null); + const h = useMetricHostsModuleContext(); + const k = useMetricK8sModuleContext(); + const { createDerivedIndexPattern } = useSourceViaHttp({ + sourceId: 'default', + type: 'metrics', + }); + + const indicies = h.sourceConfiguration.indices; + + const setupStatus = useMemo(() => { + if (props.jobType === 'kubernetes') { + return k.setupStatus; + } else { + return h.setupStatus; + } + }, [props.jobType, k.setupStatus, h.setupStatus]); + + const cleanUpAndSetUpModule = useMemo(() => { + if (props.jobType === 'kubernetes') { + return k.cleanUpAndSetUpModule; + } else { + return h.cleanUpAndSetUpModule; + } + }, [props.jobType, k.cleanUpAndSetUpModule, h.cleanUpAndSetUpModule]); + + const setUpModule = useMemo(() => { + if (props.jobType === 'kubernetes') { + return k.setUpModule; + } else { + return h.setUpModule; + } + }, [props.jobType, k.setUpModule, h.setUpModule]); + + const hasSummaries = useMemo(() => { + if (props.jobType === 'kubernetes') { + return k.jobSummaries.length > 0; + } else { + return h.jobSummaries.length > 0; + } + }, [props.jobType, k.jobSummaries, h.jobSummaries]); + + const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ + createDerivedIndexPattern, + ]); + + const updateStart = useCallback((date: Moment) => { + setStartDate(date); + }, []); + + const createJobs = useCallback(() => { + if (hasSummaries) { + cleanUpAndSetUpModule( + indicies, + moment(startDate).toDate().getTime(), + undefined, + { type: 'includeAll' }, + partitionField ? partitionField[0] : undefined + ); + } else { + setUpModule( + indicies, + moment(startDate).toDate().getTime(), + undefined, + { type: 'includeAll' }, + partitionField ? partitionField[0] : undefined + ); + } + }, [cleanUpAndSetUpModule, setUpModule, hasSummaries, indicies, partitionField, startDate]); + + const onPartitionFieldChange = useCallback((value: Array<{ label: string }>) => { + setPartitionField(value.map((v) => v.label)); + }, []); + + useEffect(() => { + if (props.jobType === 'kubernetes') { + setPartitionField(['kubernetes.namespace']); + } + }, [props.jobType]); + + useEffect(() => { + if (setupStatus.type === 'succeeded') { + goHome(); + } + }, [setupStatus, goHome]); + + return ( + <> + + +

+ +

+
+
+ + {setupStatus.type === 'pending' ? ( + + + + + + + + + ) : setupStatus.type === 'failed' ? ( + <> + + + + + + + ) : ( + <> + +

+ +

+
+ + + + + + + } + description={ + + } + > + + } + > + + + + + + + + } + description={ + + } + > + + } + compressed + > + ({ label: p })) : undefined + } + options={derivedIndexPattern.fields + .filter((f) => f.aggregatable && f.type === 'string') + .map((f) => ({ label: f.name }))} + onChange={onPartitionFieldChange} + isClearable={true} + /> + + + + + )} +
+ + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/subscription_splash_content.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/subscription_splash_content.tsx new file mode 100644 index 0000000000000..f07c37f5e7ea2 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/subscription_splash_content.tsx @@ -0,0 +1,172 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiText, + EuiButton, + EuiButtonEmpty, + EuiImage, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { LoadingPage } from '../../../../../../components/loading_page'; +import { useTrialStatus } from '../../../../../../hooks/use_trial_status'; +import { useKibana } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { euiStyled } from '../../../../../../../../observability/public'; +import { HttpStart } from '../../../../../../../../../../src/core/public'; + +export const SubscriptionSplashContent: React.FC = () => { + const { services } = useKibana<{ http: HttpStart }>(); + const { loadState, isTrialAvailable, checkTrialAvailability } = useTrialStatus(); + + useEffect(() => { + checkTrialAvailability(); + }, [checkTrialAvailability]); + + if (loadState === 'pending') { + return ( + + ); + } + + const canStartTrial = isTrialAvailable && loadState === 'resolved'; + + let title; + let description; + let cta; + + if (canStartTrial) { + title = ( + + ); + + description = ( + + ); + + cta = ( + + + + ); + } else { + title = ( + + ); + + description = ( + + ); + + cta = ( + + + + ); + } + + return ( + + + + + + +

{title}

+
+ + +

{description}

+
+ +
{cta}
+
+ + + +
+ + +

+ +

+
+ + + +
+
+
+
+ ); +}; + +const SubscriptionPage = euiStyled(EuiPage)` + height: 100% +`; + +const SubscriptionPageContent = euiStyled(EuiPageContent)` + max-width: 768px !important; +`; + +const SubscriptionPageFooter = euiStyled.div` + background: ${(props) => props.theme.eui.euiColorLightestShade}; + margin: 0 -${(props) => props.theme.eui.paddingSizes.l} -${(props) => + props.theme.eui.paddingSizes.l}; + padding: ${(props) => props.theme.eui.paddingSizes.l}; +`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx new file mode 100644 index 0000000000000..2792b6eb18b00 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import moment from 'moment'; +import { first, last } from 'lodash'; +import { EuiLoadingChart, EuiText, EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { + Axis, + Chart, + Settings, + Position, + TooltipValue, + niceTimeFormatter, + ElementClickListener, +} from '@elastic/charts'; +import { useUiSetting } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { toMetricOpt } from '../../../../../../common/snapshot_metric_i18n'; +import { MetricsExplorerAggregation } from '../../../../../../common/http_api'; +import { Color } from '../../../../../../common/color_palette'; +import { useSourceContext } from '../../../../../containers/source'; +import { useTimeline } from '../../hooks/use_timeline'; +import { useWaffleOptionsContext } from '../../hooks/use_waffle_options'; +import { useWaffleTimeContext } from '../../hooks/use_waffle_time'; +import { useWaffleFiltersContext } from '../../hooks/use_waffle_filters'; +import { MetricExplorerSeriesChart } from '../../../metrics_explorer/components/series_chart'; +import { MetricsExplorerChartType } from '../../../metrics_explorer/hooks/use_metrics_explorer_options'; +import { getTimelineChartTheme } from '../../../metrics_explorer/components/helpers/get_chart_theme'; +import { calculateDomain } from '../../../metrics_explorer/components/helpers/calculate_domain'; + +import { euiStyled } from '../../../../../../../observability/public'; +import { InfraFormatter } from '../../../../../lib/lib'; + +interface Props { + interval: string; + yAxisFormatter: InfraFormatter; + isVisible: boolean; +} + +export const Timeline: React.FC = ({ interval, yAxisFormatter, isVisible }) => { + const { sourceId } = useSourceContext(); + const { metric, nodeType, accountId, region } = useWaffleOptionsContext(); + const { currentTime, jumpToTime, stopAutoReload } = useWaffleTimeContext(); + const { filterQueryAsJson } = useWaffleFiltersContext(); + const { loading, error, timeseries, reload } = useTimeline( + filterQueryAsJson, + [metric], + nodeType, + sourceId, + currentTime, + accountId, + region, + interval, + isVisible + ); + + const metricLabel = toMetricOpt(metric.type)?.textLC; + + const chartMetric = { + color: Color.color0, + aggregation: 'avg' as MetricsExplorerAggregation, + label: metricLabel, + }; + + const dateFormatter = useMemo(() => { + if (!timeseries) return () => ''; + const firstTimestamp = first(timeseries.rows)?.timestamp; + const lastTimestamp = last(timeseries.rows)?.timestamp; + + if (firstTimestamp == null || lastTimestamp == null) { + return (value: number) => `${value}`; + } + + return niceTimeFormatter([firstTimestamp, lastTimestamp]); + }, [timeseries]); + + const isDarkMode = useUiSetting('theme:darkMode'); + const tooltipProps = { + headerFormatter: (tooltipValue: TooltipValue) => + moment(tooltipValue.value).format('Y-MM-DD HH:mm:ss.SSS'), + }; + + const dataDomain = timeseries ? calculateDomain(timeseries, [chartMetric], false) : null; + const domain = dataDomain + ? { + max: dataDomain.max * 1.1, // add 10% headroom. + min: dataDomain.min, + } + : { max: 0, min: 0 }; + + const onClickPoint: ElementClickListener = useCallback( + ([[geometryValue]]) => { + if (!Array.isArray(geometryValue)) { + const { x: timestamp } = geometryValue; + jumpToTime(timestamp); + stopAutoReload(); + } + }, + [jumpToTime, stopAutoReload] + ); + + if (loading) { + return ( + + + + + + ); + } + + if (!loading && (error || !timeseries)) { + return ( + + {error ? errorTitle : noHistoryDataTitle}} + actions={ + + {error ? retryButtonLabel : checkNewDataButtonLabel} + + } + /> + + ); + } + + return ( + + + + + + + + + + + + + + + + + + ); +}; + +const TimelineContainer = euiStyled.div` + background-color: ${(props) => props.theme.eui.euiPageBackgroundColor}; + border-top: 1px solid ${(props) => props.theme.eui.euiColorLightShade}; + height: 220px; + width: 100%; + padding: ${(props) => props.theme.eui.paddingSizes.s} ${(props) => + props.theme.eui.paddingSizes.m}; + display: flex; + flex-direction: column; +`; + +const TimelineHeader = euiStyled.div` + display: flex; + width: 100%; + padding: ${(props) => props.theme.eui.paddingSizes.s} ${(props) => + props.theme.eui.paddingSizes.m}; +`; + +const TimelineChartContainer = euiStyled.div` + padding-left: ${(props) => props.theme.eui.paddingSizes.xs}; + width: 100%; + height: 100%; +`; + +const TimelineLoadingContainer = euiStyled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100%; +`; + +const noHistoryDataTitle = i18n.translate('xpack.infra.inventoryTimeline.noHistoryDataTitle', { + defaultMessage: 'There is no history data to display.', +}); + +const errorTitle = i18n.translate('xpack.infra.inventoryTimeline.errorTitle', { + defaultMessage: 'Unable to display history data.', +}); + +const checkNewDataButtonLabel = i18n.translate( + 'xpack.infra.inventoryTimeline.checkNewDataButtonLabel', + { + defaultMessage: 'Check for new data', + } +); + +const retryButtonLabel = i18n.translate('xpack.infra.inventoryTimeline.retryButtonLabel', { + defaultMessage: 'Try again', +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx index a785cb31c3cf4..262d94d8f3674 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx @@ -27,10 +27,7 @@ import { SNAPSHOT_CUSTOM_AGGREGATIONS, SnapshotCustomAggregationRT, } from '../../../../../../../common/http_api/snapshot_api'; -import { - EuiTheme, - withTheme, -} from '../../../../../../../../../legacy/common/eui_styled_components'; +import { EuiTheme, withTheme } from '../../../../../../../../xpack_legacy/common'; interface SelectedOption { label: string; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx index e75885ccbc917..831a0cde49cfb 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx @@ -8,10 +8,7 @@ import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { getCustomMetricLabel } from '../../../../../../../common/formatters/get_custom_metric_label'; import { SnapshotCustomMetricInput } from '../../../../../../../common/http_api/snapshot_api'; -import { - EuiTheme, - withTheme, -} from '../../../../../../../../../legacy/common/eui_styled_components'; +import { EuiTheme, withTheme } from '../../../../../../../../xpack_legacy/common'; interface Props { theme: EuiTheme | undefined; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx index d1abcade5d660..956241545e8be 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx @@ -9,10 +9,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { CustomMetricMode } from './types'; import { SnapshotCustomMetricInput } from '../../../../../../../common/http_api/snapshot_api'; -import { - EuiTheme, - withTheme, -} from '../../../../../../../../../legacy/common/eui_styled_components'; +import { EuiTheme, withTheme } from '../../../../../../../../xpack_legacy/common'; interface Props { theme: EuiTheme | undefined; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts new file mode 100644 index 0000000000000..f755057d0b76d --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts @@ -0,0 +1,318 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useMemo, useState, useCallback, useEffect, useReducer } from 'react'; +import { + INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, + Sort, + Pagination, + PaginationCursor, + getMetricsHostsAnomaliesRequestPayloadRT, + MetricsHostsAnomaly, + getMetricsHostsAnomaliesSuccessReponsePayloadRT, +} from '../../../../../common/http_api/infra_ml'; +import { useTrackedPromise } from '../../../../utils/use_tracked_promise'; +import { npStart } from '../../../../legacy_singletons'; +import { decodeOrThrow } from '../../../../../common/runtime_types'; + +export type SortOptions = Sort; +export type PaginationOptions = Pick; +export type Page = number; +export type FetchNextPage = () => void; +export type FetchPreviousPage = () => void; +export type ChangeSortOptions = (sortOptions: Sort) => void; +export type ChangePaginationOptions = (paginationOptions: PaginationOptions) => void; +export type MetricsHostsAnomalies = MetricsHostsAnomaly[]; +interface PaginationCursors { + previousPageCursor: PaginationCursor; + nextPageCursor: PaginationCursor; +} + +interface ReducerState { + page: number; + lastReceivedCursors: PaginationCursors | undefined; + paginationCursor: Pagination['cursor'] | undefined; + hasNextPage: boolean; + paginationOptions: PaginationOptions; + sortOptions: Sort; + timeRange: { + start: number; + end: number; + }; + filteredDatasets?: string[]; +} + +type ReducerStateDefaults = Pick< + ReducerState, + 'page' | 'lastReceivedCursors' | 'paginationCursor' | 'hasNextPage' +>; + +type ReducerAction = + | { type: 'changePaginationOptions'; payload: { paginationOptions: PaginationOptions } } + | { type: 'changeSortOptions'; payload: { sortOptions: Sort } } + | { type: 'fetchNextPage' } + | { type: 'fetchPreviousPage' } + | { type: 'changeHasNextPage'; payload: { hasNextPage: boolean } } + | { type: 'changeLastReceivedCursors'; payload: { lastReceivedCursors: PaginationCursors } } + | { type: 'changeTimeRange'; payload: { timeRange: { start: number; end: number } } } + | { type: 'changeFilteredDatasets'; payload: { filteredDatasets?: string[] } }; + +const stateReducer = (state: ReducerState, action: ReducerAction): ReducerState => { + const resetPagination = { + page: 1, + paginationCursor: undefined, + }; + switch (action.type) { + case 'changePaginationOptions': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeSortOptions': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeHasNextPage': + return { + ...state, + ...action.payload, + }; + case 'changeLastReceivedCursors': + return { + ...state, + ...action.payload, + }; + case 'fetchNextPage': + return state.lastReceivedCursors + ? { + ...state, + page: state.page + 1, + paginationCursor: { searchAfter: state.lastReceivedCursors.nextPageCursor }, + } + : state; + case 'fetchPreviousPage': + return state.lastReceivedCursors + ? { + ...state, + page: state.page - 1, + paginationCursor: { searchBefore: state.lastReceivedCursors.previousPageCursor }, + } + : state; + case 'changeTimeRange': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeFilteredDatasets': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + default: + return state; + } +}; + +const STATE_DEFAULTS: ReducerStateDefaults = { + // NOTE: This piece of state is purely for the client side, it could be extracted out of the hook. + page: 1, + // Cursor from the last request + lastReceivedCursors: undefined, + // Cursor to use for the next request. For the first request, and therefore not paging, this will be undefined. + paginationCursor: undefined, + hasNextPage: false, +}; + +export const useMetricsHostsAnomaliesResults = ({ + endTime, + startTime, + sourceId, + defaultSortOptions, + defaultPaginationOptions, + onGetMetricsHostsAnomaliesDatasetsError, + filteredDatasets, +}: { + endTime: number; + startTime: number; + sourceId: string; + defaultSortOptions: Sort; + defaultPaginationOptions: Pick; + onGetMetricsHostsAnomaliesDatasetsError?: (error: Error) => void; + filteredDatasets?: string[]; +}) => { + const initStateReducer = (stateDefaults: ReducerStateDefaults): ReducerState => { + return { + ...stateDefaults, + paginationOptions: defaultPaginationOptions, + sortOptions: defaultSortOptions, + filteredDatasets, + timeRange: { + start: startTime, + end: endTime, + }, + }; + }; + + const [reducerState, dispatch] = useReducer(stateReducer, STATE_DEFAULTS, initStateReducer); + + const [metricsHostsAnomalies, setMetricsHostsAnomalies] = useState([]); + + const [getMetricsHostsAnomaliesRequest, getMetricsHostsAnomalies] = useTrackedPromise( + { + cancelPreviousOn: 'creation', + createPromise: async () => { + const { + timeRange: { start: queryStartTime, end: queryEndTime }, + sortOptions, + paginationOptions, + paginationCursor, + } = reducerState; + return await callGetMetricHostsAnomaliesAPI( + sourceId, + queryStartTime, + queryEndTime, + sortOptions, + { + ...paginationOptions, + cursor: paginationCursor, + } + ); + }, + onResolve: ({ data: { anomalies, paginationCursors: requestCursors, hasMoreEntries } }) => { + const { paginationCursor } = reducerState; + if (requestCursors) { + dispatch({ + type: 'changeLastReceivedCursors', + payload: { lastReceivedCursors: requestCursors }, + }); + } + // Check if we have more "next" entries. "Page" covers the "previous" scenario, + // since we need to know the page we're on anyway. + if (!paginationCursor || (paginationCursor && 'searchAfter' in paginationCursor)) { + dispatch({ type: 'changeHasNextPage', payload: { hasNextPage: hasMoreEntries } }); + } else if (paginationCursor && 'searchBefore' in paginationCursor) { + // We've requested a previous page, therefore there is a next page. + dispatch({ type: 'changeHasNextPage', payload: { hasNextPage: true } }); + } + setMetricsHostsAnomalies(anomalies); + }, + }, + [ + sourceId, + dispatch, + reducerState.timeRange, + reducerState.sortOptions, + reducerState.paginationOptions, + reducerState.paginationCursor, + reducerState.filteredDatasets, + ] + ); + + const changeSortOptions = useCallback( + (nextSortOptions: Sort) => { + dispatch({ type: 'changeSortOptions', payload: { sortOptions: nextSortOptions } }); + }, + [dispatch] + ); + + const changePaginationOptions = useCallback( + (nextPaginationOptions: PaginationOptions) => { + dispatch({ + type: 'changePaginationOptions', + payload: { paginationOptions: nextPaginationOptions }, + }); + }, + [dispatch] + ); + + // Time range has changed + useEffect(() => { + dispatch({ + type: 'changeTimeRange', + payload: { timeRange: { start: startTime, end: endTime } }, + }); + }, [startTime, endTime]); + + // Selected datasets have changed + useEffect(() => { + dispatch({ + type: 'changeFilteredDatasets', + payload: { filteredDatasets }, + }); + }, [filteredDatasets]); + + useEffect(() => { + getMetricsHostsAnomalies(); + }, [getMetricsHostsAnomalies]); // TODO: FIgure out the deps here. + + const handleFetchNextPage = useCallback(() => { + if (reducerState.lastReceivedCursors) { + dispatch({ type: 'fetchNextPage' }); + } + }, [dispatch, reducerState]); + + const handleFetchPreviousPage = useCallback(() => { + if (reducerState.lastReceivedCursors) { + dispatch({ type: 'fetchPreviousPage' }); + } + }, [dispatch, reducerState]); + + const isLoadingMetricsHostsAnomalies = useMemo( + () => getMetricsHostsAnomaliesRequest.state === 'pending', + [getMetricsHostsAnomaliesRequest.state] + ); + + const hasFailedLoadingMetricsHostsAnomalies = useMemo( + () => getMetricsHostsAnomaliesRequest.state === 'rejected', + [getMetricsHostsAnomaliesRequest.state] + ); + + return { + metricsHostsAnomalies, + getMetricsHostsAnomalies, + isLoadingMetricsHostsAnomalies, + hasFailedLoadingMetricsHostsAnomalies, + changeSortOptions, + sortOptions: reducerState.sortOptions, + changePaginationOptions, + paginationOptions: reducerState.paginationOptions, + fetchPreviousPage: reducerState.page > 1 ? handleFetchPreviousPage : undefined, + fetchNextPage: reducerState.hasNextPage ? handleFetchNextPage : undefined, + page: reducerState.page, + }; +}; + +export const callGetMetricHostsAnomaliesAPI = async ( + sourceId: string, + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) => { + const response = await npStart.http.fetch(INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, { + method: 'POST', + body: JSON.stringify( + getMetricsHostsAnomaliesRequestPayloadRT.encode({ + data: { + sourceId, + timeRange: { + startTime, + endTime, + }, + sort, + pagination, + }, + }) + ), + }); + + return decodeOrThrow(getMetricsHostsAnomaliesSuccessReponsePayloadRT)(response); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts new file mode 100644 index 0000000000000..4a7b78e1fdf92 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts @@ -0,0 +1,322 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useMemo, useState, useCallback, useEffect, useReducer } from 'react'; +import { + Sort, + Pagination, + PaginationCursor, + INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, + getMetricsK8sAnomaliesSuccessReponsePayloadRT, + getMetricsK8sAnomaliesRequestPayloadRT, + MetricsK8sAnomaly, +} from '../../../../../common/http_api/infra_ml'; +import { useTrackedPromise } from '../../../../utils/use_tracked_promise'; +import { npStart } from '../../../../legacy_singletons'; +import { decodeOrThrow } from '../../../../../common/runtime_types'; + +export type SortOptions = Sort; +export type PaginationOptions = Pick; +export type Page = number; +export type FetchNextPage = () => void; +export type FetchPreviousPage = () => void; +export type ChangeSortOptions = (sortOptions: Sort) => void; +export type ChangePaginationOptions = (paginationOptions: PaginationOptions) => void; +export type MetricsK8sAnomalies = MetricsK8sAnomaly[]; +interface PaginationCursors { + previousPageCursor: PaginationCursor; + nextPageCursor: PaginationCursor; +} + +interface ReducerState { + page: number; + lastReceivedCursors: PaginationCursors | undefined; + paginationCursor: Pagination['cursor'] | undefined; + hasNextPage: boolean; + paginationOptions: PaginationOptions; + sortOptions: Sort; + timeRange: { + start: number; + end: number; + }; + filteredDatasets?: string[]; +} + +type ReducerStateDefaults = Pick< + ReducerState, + 'page' | 'lastReceivedCursors' | 'paginationCursor' | 'hasNextPage' +>; + +type ReducerAction = + | { type: 'changePaginationOptions'; payload: { paginationOptions: PaginationOptions } } + | { type: 'changeSortOptions'; payload: { sortOptions: Sort } } + | { type: 'fetchNextPage' } + | { type: 'fetchPreviousPage' } + | { type: 'changeHasNextPage'; payload: { hasNextPage: boolean } } + | { type: 'changeLastReceivedCursors'; payload: { lastReceivedCursors: PaginationCursors } } + | { type: 'changeTimeRange'; payload: { timeRange: { start: number; end: number } } } + | { type: 'changeFilteredDatasets'; payload: { filteredDatasets?: string[] } }; + +const stateReducer = (state: ReducerState, action: ReducerAction): ReducerState => { + const resetPagination = { + page: 1, + paginationCursor: undefined, + }; + switch (action.type) { + case 'changePaginationOptions': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeSortOptions': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeHasNextPage': + return { + ...state, + ...action.payload, + }; + case 'changeLastReceivedCursors': + return { + ...state, + ...action.payload, + }; + case 'fetchNextPage': + return state.lastReceivedCursors + ? { + ...state, + page: state.page + 1, + paginationCursor: { searchAfter: state.lastReceivedCursors.nextPageCursor }, + } + : state; + case 'fetchPreviousPage': + return state.lastReceivedCursors + ? { + ...state, + page: state.page - 1, + paginationCursor: { searchBefore: state.lastReceivedCursors.previousPageCursor }, + } + : state; + case 'changeTimeRange': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeFilteredDatasets': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + default: + return state; + } +}; + +const STATE_DEFAULTS: ReducerStateDefaults = { + // NOTE: This piece of state is purely for the client side, it could be extracted out of the hook. + page: 1, + // Cursor from the last request + lastReceivedCursors: undefined, + // Cursor to use for the next request. For the first request, and therefore not paging, this will be undefined. + paginationCursor: undefined, + hasNextPage: false, +}; + +export const useMetricsK8sAnomaliesResults = ({ + endTime, + startTime, + sourceId, + defaultSortOptions, + defaultPaginationOptions, + onGetMetricsHostsAnomaliesDatasetsError, + filteredDatasets, +}: { + endTime: number; + startTime: number; + sourceId: string; + defaultSortOptions: Sort; + defaultPaginationOptions: Pick; + onGetMetricsHostsAnomaliesDatasetsError?: (error: Error) => void; + filteredDatasets?: string[]; +}) => { + const initStateReducer = (stateDefaults: ReducerStateDefaults): ReducerState => { + return { + ...stateDefaults, + paginationOptions: defaultPaginationOptions, + sortOptions: defaultSortOptions, + filteredDatasets, + timeRange: { + start: startTime, + end: endTime, + }, + }; + }; + + const [reducerState, dispatch] = useReducer(stateReducer, STATE_DEFAULTS, initStateReducer); + + const [metricsK8sAnomalies, setMetricsK8sAnomalies] = useState([]); + + const [getMetricsK8sAnomaliesRequest, getMetricsK8sAnomalies] = useTrackedPromise( + { + cancelPreviousOn: 'creation', + createPromise: async () => { + const { + timeRange: { start: queryStartTime, end: queryEndTime }, + sortOptions, + paginationOptions, + paginationCursor, + filteredDatasets: queryFilteredDatasets, + } = reducerState; + return await callGetMetricsK8sAnomaliesAPI( + sourceId, + queryStartTime, + queryEndTime, + sortOptions, + { + ...paginationOptions, + cursor: paginationCursor, + }, + queryFilteredDatasets + ); + }, + onResolve: ({ data: { anomalies, paginationCursors: requestCursors, hasMoreEntries } }) => { + const { paginationCursor } = reducerState; + if (requestCursors) { + dispatch({ + type: 'changeLastReceivedCursors', + payload: { lastReceivedCursors: requestCursors }, + }); + } + // Check if we have more "next" entries. "Page" covers the "previous" scenario, + // since we need to know the page we're on anyway. + if (!paginationCursor || (paginationCursor && 'searchAfter' in paginationCursor)) { + dispatch({ type: 'changeHasNextPage', payload: { hasNextPage: hasMoreEntries } }); + } else if (paginationCursor && 'searchBefore' in paginationCursor) { + // We've requested a previous page, therefore there is a next page. + dispatch({ type: 'changeHasNextPage', payload: { hasNextPage: true } }); + } + setMetricsK8sAnomalies(anomalies); + }, + }, + [ + sourceId, + dispatch, + reducerState.timeRange, + reducerState.sortOptions, + reducerState.paginationOptions, + reducerState.paginationCursor, + reducerState.filteredDatasets, + ] + ); + + const changeSortOptions = useCallback( + (nextSortOptions: Sort) => { + dispatch({ type: 'changeSortOptions', payload: { sortOptions: nextSortOptions } }); + }, + [dispatch] + ); + + const changePaginationOptions = useCallback( + (nextPaginationOptions: PaginationOptions) => { + dispatch({ + type: 'changePaginationOptions', + payload: { paginationOptions: nextPaginationOptions }, + }); + }, + [dispatch] + ); + + // Time range has changed + useEffect(() => { + dispatch({ + type: 'changeTimeRange', + payload: { timeRange: { start: startTime, end: endTime } }, + }); + }, [startTime, endTime]); + + // Selected datasets have changed + useEffect(() => { + dispatch({ + type: 'changeFilteredDatasets', + payload: { filteredDatasets }, + }); + }, [filteredDatasets]); + + useEffect(() => { + getMetricsK8sAnomalies(); + }, [getMetricsK8sAnomalies]); + + const handleFetchNextPage = useCallback(() => { + if (reducerState.lastReceivedCursors) { + dispatch({ type: 'fetchNextPage' }); + } + }, [dispatch, reducerState]); + + const handleFetchPreviousPage = useCallback(() => { + if (reducerState.lastReceivedCursors) { + dispatch({ type: 'fetchPreviousPage' }); + } + }, [dispatch, reducerState]); + + const isLoadingMetricsK8sAnomalies = useMemo( + () => getMetricsK8sAnomaliesRequest.state === 'pending', + [getMetricsK8sAnomaliesRequest.state] + ); + + const hasFailedLoadingMetricsK8sAnomalies = useMemo( + () => getMetricsK8sAnomaliesRequest.state === 'rejected', + [getMetricsK8sAnomaliesRequest.state] + ); + + return { + metricsK8sAnomalies, + getMetricsK8sAnomalies, + isLoadingMetricsK8sAnomalies, + hasFailedLoadingMetricsK8sAnomalies, + changeSortOptions, + sortOptions: reducerState.sortOptions, + changePaginationOptions, + paginationOptions: reducerState.paginationOptions, + fetchPreviousPage: reducerState.page > 1 ? handleFetchPreviousPage : undefined, + fetchNextPage: reducerState.hasNextPage ? handleFetchNextPage : undefined, + page: reducerState.page, + }; +}; + +export const callGetMetricsK8sAnomaliesAPI = async ( + sourceId: string, + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination, + datasets?: string[] +) => { + const response = await npStart.http.fetch(INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, { + method: 'POST', + body: JSON.stringify( + getMetricsK8sAnomaliesRequestPayloadRT.encode({ + data: { + sourceId, + timeRange: { + startTime, + endTime, + }, + sort, + pagination, + datasets, + }, + }) + ), + }); + + return decodeOrThrow(getMetricsK8sAnomaliesSuccessReponsePayloadRT)(response); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts index 06b53d531f53c..702213516c123 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts @@ -43,7 +43,7 @@ export function useSnapshot( const timerange: InfraTimerangeInput = { interval: '1m', to: currentTime, - from: currentTime - 360 * 1000, + from: currentTime - 1200 * 1000, lookbackSize: 20, }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts new file mode 100644 index 0000000000000..acf9011ac7ddd --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { first } from 'lodash'; +import { useEffect, useMemo, useCallback } from 'react'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getIntervalInSeconds } from '../../../../../server/utils/get_interval_in_seconds'; +import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; +import { useHTTPRequest } from '../../../../hooks/use_http_request'; +import { + SnapshotNodeResponseRT, + SnapshotNodeResponse, + SnapshotRequest, + InfraTimerangeInput, +} from '../../../../../common/http_api/snapshot_api'; +import { + InventoryItemType, + SnapshotMetricType, +} from '../../../../../common/inventory_models/types'; + +const ONE_MINUTE = 60; +const ONE_HOUR = ONE_MINUTE * 60; +const ONE_DAY = ONE_HOUR * 24; +const ONE_WEEK = ONE_DAY * 7; +const ONE_MONTH = ONE_DAY * 30; + +const getDisplayInterval = (interval: string | undefined) => { + if (interval) { + const intervalInSeconds = getIntervalInSeconds(interval); + if (intervalInSeconds < 300) return '5m'; + } + return interval; +}; + +const getTimeLengthFromInterval = (interval: string | undefined) => { + if (interval) { + const intervalInSeconds = getIntervalInSeconds(interval); + // Get up to 288 datapoints based on interval + const timeLength = + intervalInSeconds <= ONE_MINUTE * 15 + ? ONE_DAY + : intervalInSeconds <= ONE_MINUTE * 35 + ? ONE_DAY * 3 + : intervalInSeconds <= ONE_HOUR * 2.5 + ? ONE_WEEK + : ONE_MONTH; + return { timeLength, intervalInSeconds }; + } else { + return { timeLength: 0, intervalInSeconds: 0 }; + } +}; + +export function useTimeline( + filterQuery: string | null | undefined, + metrics: Array<{ type: SnapshotMetricType }>, + nodeType: InventoryItemType, + sourceId: string, + currentTime: number, + accountId: string, + region: string, + interval: string | undefined, + shouldReload: boolean +) { + const decodeResponse = (response: any) => { + return pipe( + SnapshotNodeResponseRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); + }; + + const displayInterval = useMemo(() => getDisplayInterval(interval), [interval]); + + const timeLengthResult = useMemo(() => getTimeLengthFromInterval(displayInterval), [ + displayInterval, + ]); + const { timeLength, intervalInSeconds } = timeLengthResult; + + const timerange: InfraTimerangeInput = { + interval: displayInterval ?? '', + to: currentTime + intervalInSeconds * 1000, + from: currentTime - timeLength * 1000, + ignoreLookback: true, + forceInterval: true, + }; + + const { error, loading, response, makeRequest } = useHTTPRequest( + '/api/metrics/snapshot', + 'POST', + JSON.stringify({ + metrics, + groupBy: null, + nodeType, + timerange, + filterQuery, + sourceId, + accountId, + region, + includeTimeseries: true, + } as SnapshotRequest), + decodeResponse + ); + + const loadData = useCallback(() => { + if (shouldReload) return makeRequest(); + return Promise.resolve(); + }, [makeRequest, shouldReload]); + + useEffect(() => { + (async () => { + if (timeLength) { + await loadData(); + } + })(); + }, [loadData, timeLength]); + + const timeseries = response + ? first(response.nodes.map((node) => first(node.metrics)?.timeseries)) + : null; + + return { + error: (error && error.message) || null, + loading: !interval ? true : loading, + timeseries, + reload: makeRequest, + }; +} diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/get_chart_theme.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/get_chart_theme.ts index 42469ffb5ee9a..bb6a70f65bb97 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/get_chart_theme.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/get_chart_theme.ts @@ -4,8 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts'; +import { + Theme, + PartialTheme, + LIGHT_THEME, + DARK_THEME, + mergeWithDefaultTheme, +} from '@elastic/charts'; export function getChartTheme(isDarkMode: boolean): Theme { return isDarkMode ? DARK_THEME : LIGHT_THEME; } + +export function getTimelineChartTheme(isDarkMode: boolean): Theme { + return isDarkMode ? DARK_THEME : mergeWithDefaultTheme(TIMELINE_LIGHT_THEME, LIGHT_THEME); +} + +const TIMELINE_LIGHT_THEME: PartialTheme = { + crosshair: { + band: { + fill: '#D3DAE6', + }, + }, + axes: { + gridLine: { + horizontal: { + stroke: '#eaeaea', + }, + }, + }, +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx index b33fe5c232f01..f566e5253c615 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx @@ -18,6 +18,10 @@ import { resp, createSeries, } from '../../../../utils/fixtures/metrics_explorer'; +import { MetricsExplorerOptions, MetricsExplorerTimeOptions } from './use_metrics_explorer_options'; +import { SourceQuery } from '../../../../../common/graphql/types'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; +import { HttpHandler } from 'kibana/public'; const mockedFetch = jest.fn(); @@ -31,7 +35,16 @@ const renderUseMetricsExplorerDataHook = () => { return {children}; }; return renderHook( - (props) => + (props: { + options: MetricsExplorerOptions; + source: SourceQuery.Query['source']['configuration'] | undefined; + derivedIndexPattern: IIndexPattern; + timeRange: MetricsExplorerTimeOptions; + afterKey: string | null | Record; + signal: any; + fetch?: HttpHandler; + shouldLoadImmediately?: boolean; + }) => useMetricsExplorerData( props.options, props.source, diff --git a/x-pack/plugins/infra/public/utils/url_state.tsx b/x-pack/plugins/infra/public/utils/url_state.tsx index bf4cfbaf05965..5abd35afb7525 100644 --- a/x-pack/plugins/infra/public/utils/url_state.tsx +++ b/x-pack/plugins/infra/public/utils/url_state.tsx @@ -156,16 +156,14 @@ export const replaceStateKeyInQueryString = ( urlState: UrlState | undefined ) => (queryString: string) => { const previousQueryValues = parse(queryString, { sort: false }); - const encodedUrlState = - typeof urlState !== 'undefined' ? encodeRisonUrlState(urlState) : undefined; - - return stringify( - url.encodeQuery({ - ...previousQueryValues, - [stateKey]: encodedUrlState, - }), - { sort: false, encode: false } - ); + const newValue = + typeof urlState === 'undefined' + ? previousQueryValues + : { + ...previousQueryValues, + [stateKey]: encodeRisonUrlState(urlState), + }; + return stringify(url.encodeQuery(newValue), { sort: false, encode: false }); }; const replaceQueryStringInLocation = (location: Location, queryString: string): Location => { diff --git a/x-pack/plugins/infra/public/utils/use_url_state.ts b/x-pack/plugins/infra/public/utils/use_url_state.ts index ab0ca1311194f..dd1cc9aeef9e4 100644 --- a/x-pack/plugins/infra/public/utils/use_url_state.ts +++ b/x-pack/plugins/infra/public/utils/use_url_state.ts @@ -111,16 +111,15 @@ export const replaceStateKeyInQueryString = ( urlState: UrlState | undefined ) => (queryString: string) => { const previousQueryValues = parse(queryString, { sort: false }); - const encodedUrlState = - typeof urlState !== 'undefined' ? encodeRisonUrlState(urlState) : undefined; - - return stringify( - url.encodeQuery({ - ...previousQueryValues, - [stateKey]: encodedUrlState, - }), - { sort: false, encode: false } - ); + const newValue = + typeof urlState === 'undefined' + ? previousQueryValues + : { + ...previousQueryValues, + [stateKey]: encodeRisonUrlState(urlState), + }; + + return stringify(url.encodeQuery(newValue), { sort: false, encode: false }); }; const replaceQueryStringInLocation = (location: Location, queryString: string): Location => { diff --git a/x-pack/plugins/infra/server/infra_server.ts b/x-pack/plugins/infra/server/infra_server.ts index a72e40e25b479..1d89b7be43296 100644 --- a/x-pack/plugins/infra/server/infra_server.ts +++ b/x-pack/plugins/infra/server/infra_server.ts @@ -13,6 +13,7 @@ import { InfraBackendLibs } from './lib/infra_types'; import { initGetLogEntryCategoriesRoute, initGetLogEntryCategoryDatasetsRoute, + initGetLogEntryCategoryDatasetsStatsRoute, initGetLogEntryCategoryExamplesRoute, initGetLogEntryRateRoute, initGetLogEntryExamplesRoute, @@ -21,6 +22,8 @@ import { initGetLogEntryAnomaliesRoute, initGetLogEntryAnomaliesDatasetsRoute, } from './routes/log_analysis'; +import { initGetK8sAnomaliesRoute } from './routes/infra_ml'; +import { initGetHostsAnomaliesRoute } from './routes/infra_ml'; import { initMetricExplorerRoute } from './routes/metrics_explorer'; import { initMetadataRoute } from './routes/metadata'; import { initSnapshotRoute } from './routes/snapshot'; @@ -52,10 +55,13 @@ export const initInfraServer = (libs: InfraBackendLibs) => { initIpToHostName(libs); initGetLogEntryCategoriesRoute(libs); initGetLogEntryCategoryDatasetsRoute(libs); + initGetLogEntryCategoryDatasetsStatsRoute(libs); initGetLogEntryCategoryExamplesRoute(libs); initGetLogEntryRateRoute(libs); initGetLogEntryAnomaliesRoute(libs); initGetLogEntryAnomaliesDatasetsRoute(libs); + initGetK8sAnomaliesRoute(libs); + initGetHostsAnomaliesRoute(libs); initSnapshotRoute(libs); initNodeDetailsRoute(libs); initSourceRoute(libs); diff --git a/x-pack/plugins/infra/server/lib/infra_ml/common.ts b/x-pack/plugins/infra/server/lib/infra_ml/common.ts new file mode 100644 index 0000000000000..4d2be94c7cd62 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/common.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import type { MlAnomalyDetectors, MlSystem } from '../../types'; +import { NoLogAnalysisMlJobError } from './errors'; + +import { + CompositeDatasetKey, + createLogEntryDatasetsQuery, + LogEntryDatasetBucket, + logEntryDatasetsResponseRT, +} from './queries/log_entry_data_sets'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import { startTracingSpan, TracingSpan } from '../../../common/performance_tracing'; + +export async function fetchMlJob(mlAnomalyDetectors: MlAnomalyDetectors, jobId: string) { + const finalizeMlGetJobSpan = startTracingSpan('Fetch ml job from ES'); + const { + jobs: [mlJob], + } = await mlAnomalyDetectors.jobs(jobId); + + const mlGetJobSpan = finalizeMlGetJobSpan(); + + if (mlJob == null) { + throw new NoLogAnalysisMlJobError(`Failed to find ml job ${jobId}.`); + } + + return { + mlJob, + timing: { + spans: [mlGetJobSpan], + }, + }; +} + +const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000; + +// Finds datasets related to ML job ids +export async function getLogEntryDatasets( + mlSystem: MlSystem, + startTime: number, + endTime: number, + jobIds: string[] +) { + const finalizeLogEntryDatasetsSpan = startTracingSpan('get data sets'); + + let logEntryDatasetBuckets: LogEntryDatasetBucket[] = []; + let afterLatestBatchKey: CompositeDatasetKey | undefined; + let esSearchSpans: TracingSpan[] = []; + + while (true) { + const finalizeEsSearchSpan = startTracingSpan('fetch log entry dataset batch from ES'); + + const logEntryDatasetsResponse = decodeOrThrow(logEntryDatasetsResponseRT)( + await mlSystem.mlAnomalySearch( + createLogEntryDatasetsQuery( + jobIds, + startTime, + endTime, + COMPOSITE_AGGREGATION_BATCH_SIZE, + afterLatestBatchKey + ) + ) + ); + + const { after_key: afterKey, buckets: latestBatchBuckets = [] } = + logEntryDatasetsResponse.aggregations?.dataset_buckets ?? {}; + + logEntryDatasetBuckets = [...logEntryDatasetBuckets, ...latestBatchBuckets]; + afterLatestBatchKey = afterKey; + esSearchSpans = [...esSearchSpans, finalizeEsSearchSpan()]; + + if (latestBatchBuckets.length < COMPOSITE_AGGREGATION_BATCH_SIZE) { + break; + } + } + + const logEntryDatasetsSpan = finalizeLogEntryDatasetsSpan(); + + return { + data: logEntryDatasetBuckets.map((logEntryDatasetBucket) => logEntryDatasetBucket.key.dataset), + timing: { + spans: [logEntryDatasetsSpan, ...esSearchSpans], + }, + }; +} diff --git a/x-pack/plugins/infra/server/lib/infra_ml/errors.ts b/x-pack/plugins/infra/server/lib/infra_ml/errors.ts new file mode 100644 index 0000000000000..ad46ebf710266 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/errors.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable max-classes-per-file */ + +import { + UnknownMLCapabilitiesError, + InsufficientMLCapabilities, + MLPrivilegesUninitialized, +} from '../../../../ml/server'; + +export class NoLogAnalysisMlJobError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class InsufficientLogAnalysisMlJobConfigurationError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class UnknownCategoryError extends Error { + constructor(categoryId: number) { + super(`Unknown ml category ${categoryId}`); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class InsufficientAnomalyMlJobsConfigured extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export const isMlPrivilegesError = (error: any) => { + return ( + error instanceof UnknownMLCapabilitiesError || + error instanceof InsufficientMLCapabilities || + error instanceof MLPrivilegesUninitialized + ); +}; diff --git a/x-pack/plugins/security_solution/server/graphql/kpi_hosts/index.ts b/x-pack/plugins/infra/server/lib/infra_ml/index.ts similarity index 68% rename from x-pack/plugins/security_solution/server/graphql/kpi_hosts/index.ts rename to x-pack/plugins/infra/server/lib/infra_ml/index.ts index cb0f2be52adc7..536f0a44d5890 100644 --- a/x-pack/plugins/security_solution/server/graphql/kpi_hosts/index.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/index.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createKpiHostsResolvers } from './resolvers'; -export { kpiHostsSchema } from './schema.gql'; +export * from './errors'; +export * from './metrics_hosts_anomalies'; +export * from './metrics_k8s_anomalies'; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts new file mode 100644 index 0000000000000..e0afa458aac88 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts @@ -0,0 +1,289 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext } from 'src/core/server'; +import { InfraRequestHandlerContext } from '../../types'; +import { TracingSpan, startTracingSpan } from '../../../common/performance_tracing'; +import { fetchMlJob, getLogEntryDatasets } from './common'; +import { getJobId, metricsHostsJobTypes } from '../../../common/infra_ml'; +import { Sort, Pagination } from '../../../common/http_api/infra_ml'; +import type { MlSystem, MlAnomalyDetectors } from '../../types'; +import { InsufficientAnomalyMlJobsConfigured, isMlPrivilegesError } from './errors'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import { + metricsHostsAnomaliesResponseRT, + createMetricsHostsAnomaliesQuery, +} from './queries/metrics_hosts_anomalies'; + +interface MappedAnomalyHit { + id: string; + anomalyScore: number; + dataset: string; + typical: number; + actual: number; + jobId: string; + startTime: number; + duration: number; + hostName: string[]; + categoryId?: string; +} + +async function getCompatibleAnomaliesJobIds( + spaceId: string, + sourceId: string, + mlAnomalyDetectors: MlAnomalyDetectors +) { + const metricsHostsJobIds = metricsHostsJobTypes.map((jt) => getJobId(spaceId, sourceId, jt)); + + const jobIds: string[] = []; + let jobSpans: TracingSpan[] = []; + + try { + await Promise.all( + metricsHostsJobIds.map((id) => { + return (async () => { + const { + timing: { spans }, + } = await fetchMlJob(mlAnomalyDetectors, id); + jobIds.push(id); + jobSpans = [...jobSpans, ...spans]; + })(); + }) + ); + } catch (e) { + if (isMlPrivilegesError(e)) { + throw e; + } + // An error is also thrown when no jobs are found + } + + return { + jobIds, + timing: { spans: jobSpans }, + }; +} + +export async function getMetricsHostsAnomalies( + context: RequestHandlerContext & { infra: Required }, + sourceId: string, + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) { + const finalizeMetricsHostsAnomaliesSpan = startTracingSpan('get metrics hosts entry anomalies'); + + const { + jobIds, + timing: { spans: jobSpans }, + } = await getCompatibleAnomaliesJobIds( + context.infra.spaceId, + sourceId, + context.infra.mlAnomalyDetectors + ); + + if (jobIds.length === 0) { + throw new InsufficientAnomalyMlJobsConfigured( + 'Metrics Hosts ML jobs need to be configured to search anomalies' + ); + } + + try { + const { + anomalies, + paginationCursors, + hasMoreEntries, + timing: { spans: fetchLogEntryAnomaliesSpans }, + } = await fetchMetricsHostsAnomalies( + context.infra.mlSystem, + jobIds, + startTime, + endTime, + sort, + pagination + ); + + const data = anomalies.map((anomaly) => { + const { jobId } = anomaly; + + return parseAnomalyResult(anomaly, jobId); + }); + + const metricsHostsAnomaliesSpan = finalizeMetricsHostsAnomaliesSpan(); + + return { + data, + paginationCursors, + hasMoreEntries, + timing: { + spans: [metricsHostsAnomaliesSpan, ...jobSpans, ...fetchLogEntryAnomaliesSpans], + }, + }; + } catch (e) { + throw new Error(e); + } +} + +const parseAnomalyResult = (anomaly: MappedAnomalyHit, jobId: string) => { + const { + id, + anomalyScore, + dataset, + typical, + actual, + duration, + hostName, + startTime: anomalyStartTime, + } = anomaly; + + return { + id, + anomalyScore, + dataset, + typical, + actual, + duration, + hostName, + startTime: anomalyStartTime, + type: 'metrics_hosts' as const, + jobId, + }; +}; + +async function fetchMetricsHostsAnomalies( + mlSystem: MlSystem, + jobIds: string[], + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) { + // We'll request 1 extra entry on top of our pageSize to determine if there are + // more entries to be fetched. This avoids scenarios where the client side can't + // determine if entries.length === pageSize actually means there are more entries / next page + // or not. + const expandedPagination = { ...pagination, pageSize: pagination.pageSize + 1 }; + + const finalizeFetchLogEntryAnomaliesSpan = startTracingSpan('fetch metrics hosts anomalies'); + + // console.log( + // 'data', + // JSON.stringify( + // await mlSystem.mlAnomalySearch( + // createMetricsHostsAnomaliesQuery(jobIds, startTime, endTime, sort, expandedPagination) + // ), + // null, + // 2 + // ) + // ); + const results = decodeOrThrow(metricsHostsAnomaliesResponseRT)( + await mlSystem.mlAnomalySearch( + createMetricsHostsAnomaliesQuery(jobIds, startTime, endTime, sort, expandedPagination) + ) + ); + + const { + hits: { hits }, + } = results; + const hasMoreEntries = hits.length > pagination.pageSize; + + // An extra entry was found and hasMoreEntries has been determined, the extra entry can be removed. + if (hasMoreEntries) { + hits.pop(); + } + + // To "search_before" the sort order will have been reversed for ES. + // The results are now reversed back, to match the requested sort. + if (pagination.cursor && 'searchBefore' in pagination.cursor) { + hits.reverse(); + } + + const paginationCursors = + hits.length > 0 + ? { + previousPageCursor: hits[0].sort, + nextPageCursor: hits[hits.length - 1].sort, + } + : undefined; + + const anomalies = hits.map((result) => { + const { + // eslint-disable-next-line @typescript-eslint/naming-convention + job_id, + record_score: anomalyScore, + typical, + actual, + bucket_span: duration, + timestamp: anomalyStartTime, + by_field_value: categoryId, + } = result._source; + + return { + id: result._id, + anomalyScore, + dataset: '', + typical: typical[0], + actual: actual[0], + jobId: job_id, + hostName: result._source['host.name'], + startTime: anomalyStartTime, + duration: duration * 1000, + categoryId, + }; + }); + + const fetchLogEntryAnomaliesSpan = finalizeFetchLogEntryAnomaliesSpan(); + + return { + anomalies, + paginationCursors, + hasMoreEntries, + timing: { + spans: [fetchLogEntryAnomaliesSpan], + }, + }; +} + +// TODO: FIgure out why we need datasets +export async function getMetricsHostsAnomaliesDatasets( + context: { + infra: { + mlSystem: MlSystem; + mlAnomalyDetectors: MlAnomalyDetectors; + spaceId: string; + }; + }, + sourceId: string, + startTime: number, + endTime: number +) { + const { + jobIds, + timing: { spans: jobSpans }, + } = await getCompatibleAnomaliesJobIds( + context.infra.spaceId, + sourceId, + context.infra.mlAnomalyDetectors + ); + + if (jobIds.length === 0) { + throw new InsufficientAnomalyMlJobsConfigured( + 'Log rate or categorisation ML jobs need to be configured to search for anomaly datasets' + ); + } + + const { + data: datasets, + timing: { spans: datasetsSpans }, + } = await getLogEntryDatasets(context.infra.mlSystem, startTime, endTime, jobIds); + + return { + datasets, + timing: { + spans: [...jobSpans, ...datasetsSpans], + }, + }; +} diff --git a/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts new file mode 100644 index 0000000000000..29507900e1847 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts @@ -0,0 +1,272 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext } from 'src/core/server'; +import { InfraRequestHandlerContext } from '../../types'; +import { TracingSpan, startTracingSpan } from '../../../common/performance_tracing'; +import { fetchMlJob, getLogEntryDatasets } from './common'; +import { getJobId, metricsK8SJobTypes } from '../../../common/infra_ml'; +import { Sort, Pagination } from '../../../common/http_api/infra_ml'; +import type { MlSystem, MlAnomalyDetectors } from '../../types'; +import { InsufficientAnomalyMlJobsConfigured, isMlPrivilegesError } from './errors'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import { + metricsK8sAnomaliesResponseRT, + createMetricsK8sAnomaliesQuery, +} from './queries/metrics_k8s_anomalies'; + +interface MappedAnomalyHit { + id: string; + anomalyScore: number; + // dataset: string; + typical: number; + actual: number; + jobId: string; + startTime: number; + duration: number; + categoryId?: string; +} + +async function getCompatibleAnomaliesJobIds( + spaceId: string, + sourceId: string, + mlAnomalyDetectors: MlAnomalyDetectors +) { + const metricsK8sJobIds = metricsK8SJobTypes.map((jt) => getJobId(spaceId, sourceId, jt)); + + const jobIds: string[] = []; + let jobSpans: TracingSpan[] = []; + + try { + await Promise.all( + metricsK8sJobIds.map((id) => { + return (async () => { + const { + timing: { spans }, + } = await fetchMlJob(mlAnomalyDetectors, id); + jobIds.push(id); + jobSpans = [...jobSpans, ...spans]; + })(); + }) + ); + } catch (e) { + if (isMlPrivilegesError(e)) { + throw e; + } + // An error is also thrown when no jobs are found + } + + return { + jobIds, + timing: { spans: jobSpans }, + }; +} + +export async function getMetricK8sAnomalies( + context: RequestHandlerContext & { infra: Required }, + sourceId: string, + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) { + const finalizeMetricsK8sAnomaliesSpan = startTracingSpan('get metrics k8s entry anomalies'); + + const { + jobIds, + timing: { spans: jobSpans }, + } = await getCompatibleAnomaliesJobIds( + context.infra.spaceId, + sourceId, + context.infra.mlAnomalyDetectors + ); + + if (jobIds.length === 0) { + throw new InsufficientAnomalyMlJobsConfigured( + 'Log rate or categorisation ML jobs need to be configured to search anomalies' + ); + } + + const { + anomalies, + paginationCursors, + hasMoreEntries, + timing: { spans: fetchLogEntryAnomaliesSpans }, + } = await fetchMetricK8sAnomalies( + context.infra.mlSystem, + jobIds, + startTime, + endTime, + sort, + pagination + ); + + const data = anomalies.map((anomaly) => { + const { jobId } = anomaly; + + return parseAnomalyResult(anomaly, jobId); + }); + + const metricsK8sAnomaliesSpan = finalizeMetricsK8sAnomaliesSpan(); + + return { + data, + paginationCursors, + hasMoreEntries, + timing: { + spans: [metricsK8sAnomaliesSpan, ...jobSpans, ...fetchLogEntryAnomaliesSpans], + }, + }; +} + +const parseAnomalyResult = (anomaly: MappedAnomalyHit, jobId: string) => { + const { + id, + anomalyScore, + // dataset, + typical, + actual, + duration, + startTime: anomalyStartTime, + } = anomaly; + + return { + id, + anomalyScore, + // dataset, + typical, + actual, + duration, + startTime: anomalyStartTime, + type: 'metrics_k8s' as const, + jobId, + }; +}; + +async function fetchMetricK8sAnomalies( + mlSystem: MlSystem, + jobIds: string[], + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) { + // We'll request 1 extra entry on top of our pageSize to determine if there are + // more entries to be fetched. This avoids scenarios where the client side can't + // determine if entries.length === pageSize actually means there are more entries / next page + // or not. + const expandedPagination = { ...pagination, pageSize: pagination.pageSize + 1 }; + + const finalizeFetchLogEntryAnomaliesSpan = startTracingSpan('fetch metrics k8s anomalies'); + + const results = decodeOrThrow(metricsK8sAnomaliesResponseRT)( + await mlSystem.mlAnomalySearch( + createMetricsK8sAnomaliesQuery(jobIds, startTime, endTime, sort, expandedPagination) + ) + ); + + const { + hits: { hits }, + } = results; + const hasMoreEntries = hits.length > pagination.pageSize; + + // An extra entry was found and hasMoreEntries has been determined, the extra entry can be removed. + if (hasMoreEntries) { + hits.pop(); + } + + // To "search_before" the sort order will have been reversed for ES. + // The results are now reversed back, to match the requested sort. + if (pagination.cursor && 'searchBefore' in pagination.cursor) { + hits.reverse(); + } + + const paginationCursors = + hits.length > 0 + ? { + previousPageCursor: hits[0].sort, + nextPageCursor: hits[hits.length - 1].sort, + } + : undefined; + + const anomalies = hits.map((result) => { + const { + // eslint-disable-next-line @typescript-eslint/naming-convention + job_id, + record_score: anomalyScore, + typical, + actual, + // partition_field_value: dataset, + bucket_span: duration, + timestamp: anomalyStartTime, + by_field_value: categoryId, + } = result._source; + + return { + id: result._id, + anomalyScore, + // dataset, + typical: typical[0], + actual: actual[0], + jobId: job_id, + startTime: anomalyStartTime, + duration: duration * 1000, + categoryId, + }; + }); + + const fetchLogEntryAnomaliesSpan = finalizeFetchLogEntryAnomaliesSpan(); + + return { + anomalies, + paginationCursors, + hasMoreEntries, + timing: { + spans: [fetchLogEntryAnomaliesSpan], + }, + }; +} + +// TODO: FIgure out why we need datasets +export async function getMetricK8sAnomaliesDatasets( + context: { + infra: { + mlSystem: MlSystem; + mlAnomalyDetectors: MlAnomalyDetectors; + spaceId: string; + }; + }, + sourceId: string, + startTime: number, + endTime: number +) { + const { + jobIds, + timing: { spans: jobSpans }, + } = await getCompatibleAnomaliesJobIds( + context.infra.spaceId, + sourceId, + context.infra.mlAnomalyDetectors + ); + + if (jobIds.length === 0) { + throw new InsufficientAnomalyMlJobsConfigured( + 'Log rate or categorisation ML jobs need to be configured to search for anomaly datasets' + ); + } + + const { + data: datasets, + timing: { spans: datasetsSpans }, + } = await getLogEntryDatasets(context.infra.mlSystem, startTime, endTime, jobIds); + + return { + datasets, + timing: { + spans: [...jobSpans, ...datasetsSpans], + }, + }; +} diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts new file mode 100644 index 0000000000000..63e39ef022392 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const defaultRequestParameters = { + allowNoIndices: true, + ignoreUnavailable: true, + trackScores: false, + trackTotalHits: false, +}; + +export const createJobIdFilters = (jobId: string) => [ + { + term: { + job_id: { + value: jobId, + }, + }, + }, +]; + +export const createJobIdsFilters = (jobIds: string[]) => [ + { + terms: { + job_id: jobIds, + }, + }, +]; + +export const createTimeRangeFilters = (startTime: number, endTime: number) => [ + { + range: { + timestamp: { + gte: startTime, + lte: endTime, + }, + }, + }, +]; + +export const createResultTypeFilters = (resultTypes: Array<'model_plot' | 'record'>) => [ + { + terms: { + result_type: resultTypes, + }, + }, +]; + +export const createCategoryIdFilters = (categoryIds: number[]) => [ + { + terms: { + category_id: categoryIds, + }, + }, +]; + +export const createDatasetsFilters = (datasets?: string[]) => + datasets && datasets.length > 0 + ? [ + { + terms: { + partition_field_value: datasets, + }, + }, + ] + : []; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/index.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/index.ts new file mode 100644 index 0000000000000..5a42011e1cea1 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './metrics_k8s_anomalies'; +export * from './metrics_hosts_anomalies'; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/log_entry_data_sets.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/log_entry_data_sets.ts new file mode 100644 index 0000000000000..53971a91d86b1 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/log_entry_data_sets.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { + createJobIdsFilters, + createResultTypeFilters, + createTimeRangeFilters, + defaultRequestParameters, +} from './common'; + +export const createLogEntryDatasetsQuery = ( + jobIds: string[], + startTime: number, + endTime: number, + size: number, + afterKey?: CompositeDatasetKey +) => ({ + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: [ + ...createJobIdsFilters(jobIds), + ...createTimeRangeFilters(startTime, endTime), + ...createResultTypeFilters(['model_plot']), + ], + }, + }, + aggs: { + dataset_buckets: { + composite: { + after: afterKey, + size, + sources: [ + { + dataset: { + terms: { + field: 'partition_field_value', + order: 'asc', + }, + }, + }, + ], + }, + }, + }, + }, + size: 0, +}); + +const compositeDatasetKeyRT = rt.type({ + dataset: rt.string, +}); + +export type CompositeDatasetKey = rt.TypeOf; + +const logEntryDatasetBucketRT = rt.type({ + key: compositeDatasetKeyRT, +}); + +export type LogEntryDatasetBucket = rt.TypeOf; + +export const logEntryDatasetsResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.partial({ + aggregations: rt.type({ + dataset_buckets: rt.intersection([ + rt.type({ + buckets: rt.array(logEntryDatasetBucketRT), + }), + rt.partial({ + after_key: compositeDatasetKeyRT, + }), + ]), + }), + }), +]); + +export type LogEntryDatasetsResponse = rt.TypeOf; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts new file mode 100644 index 0000000000000..b61119b60bc18 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { + createJobIdsFilters, + createTimeRangeFilters, + createResultTypeFilters, + defaultRequestParameters, +} from './common'; +import { Sort, Pagination } from '../../../../common/http_api/infra_ml'; + +// TODO: Reassess validity of this against ML docs +const TIEBREAKER_FIELD = '_doc'; + +const sortToMlFieldMap = { + dataset: 'partition_field_value', + anomalyScore: 'record_score', + startTime: 'timestamp', +}; + +export const createMetricsHostsAnomaliesQuery = ( + jobIds: string[], + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) => { + const { field } = sort; + const { pageSize } = pagination; + + const filters = [ + ...createJobIdsFilters(jobIds), + ...createTimeRangeFilters(startTime, endTime), + ...createResultTypeFilters(['record']), + ]; + + const sourceFields = [ + 'job_id', + 'record_score', + 'typical', + 'actual', + 'partition_field_value', + 'timestamp', + 'bucket_span', + 'by_field_value', + 'host.name', + 'influencers.influencer_field_name', + 'influencers.influencer_field_values', + ]; + + const { querySortDirection, queryCursor } = parsePaginationCursor(sort, pagination); + + const sortOptions = [ + { [sortToMlFieldMap[field]]: querySortDirection }, + { [TIEBREAKER_FIELD]: querySortDirection }, // Tiebreaker + ]; + + const resultsQuery = { + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: filters, + }, + }, + search_after: queryCursor, + sort: sortOptions, + size: pageSize, + _source: sourceFields, + }, + }; + + return resultsQuery; +}; + +export const metricsHostsAnomalyHitRT = rt.type({ + _id: rt.string, + _source: rt.intersection([ + rt.type({ + job_id: rt.string, + record_score: rt.number, + typical: rt.array(rt.number), + actual: rt.array(rt.number), + 'host.name': rt.array(rt.string), + bucket_span: rt.number, + timestamp: rt.number, + }), + rt.partial({ + by_field_value: rt.string, + }), + ]), + sort: rt.tuple([rt.union([rt.string, rt.number]), rt.union([rt.string, rt.number])]), +}); + +export type MetricsHostsAnomalyHit = rt.TypeOf; + +export const metricsHostsAnomaliesResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.type({ + hits: rt.type({ + hits: rt.array(metricsHostsAnomalyHitRT), + }), + }), +]); + +export type MetricsHostsAnomaliesResponseRT = rt.TypeOf; + +const parsePaginationCursor = (sort: Sort, pagination: Pagination) => { + const { cursor } = pagination; + const { direction } = sort; + + if (!cursor) { + return { querySortDirection: direction, queryCursor: undefined }; + } + + // We will always use ES's search_after to paginate, to mimic "search_before" behaviour we + // need to reverse the user's chosen search direction for the ES query. + if ('searchBefore' in cursor) { + return { + querySortDirection: direction === 'desc' ? 'asc' : 'desc', + queryCursor: cursor.searchBefore, + }; + } else { + return { querySortDirection: direction, queryCursor: cursor.searchAfter }; + } +}; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts new file mode 100644 index 0000000000000..84ed8b064c5ca --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { + createJobIdsFilters, + createTimeRangeFilters, + createResultTypeFilters, + defaultRequestParameters, +} from './common'; +import { Sort, Pagination } from '../../../../common/http_api/infra_ml'; + +// TODO: Reassess validity of this against ML docs +const TIEBREAKER_FIELD = '_doc'; + +const sortToMlFieldMap = { + dataset: 'partition_field_value', + anomalyScore: 'record_score', + startTime: 'timestamp', +}; + +export const createMetricsK8sAnomaliesQuery = ( + jobIds: string[], + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) => { + const { field } = sort; + const { pageSize } = pagination; + + const filters = [ + ...createJobIdsFilters(jobIds), + ...createTimeRangeFilters(startTime, endTime), + ...createResultTypeFilters(['record']), + ]; + + const sourceFields = [ + 'job_id', + 'record_score', + 'typical', + 'actual', + 'partition_field_value', + 'timestamp', + 'bucket_span', + 'by_field_value', + ]; + + const { querySortDirection, queryCursor } = parsePaginationCursor(sort, pagination); + + const sortOptions = [ + { [sortToMlFieldMap[field]]: querySortDirection }, + { [TIEBREAKER_FIELD]: querySortDirection }, // Tiebreaker + ]; + + const resultsQuery = { + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: filters, + }, + }, + search_after: queryCursor, + sort: sortOptions, + size: pageSize, + _source: sourceFields, + }, + }; + + return resultsQuery; +}; + +export const metricsK8sAnomalyHitRT = rt.type({ + _id: rt.string, + _source: rt.intersection([ + rt.type({ + job_id: rt.string, + record_score: rt.number, + typical: rt.array(rt.number), + actual: rt.array(rt.number), + // partition_field_value: rt.string, + bucket_span: rt.number, + timestamp: rt.number, + }), + rt.partial({ + by_field_value: rt.string, + }), + ]), + sort: rt.tuple([rt.union([rt.string, rt.number]), rt.union([rt.string, rt.number])]), +}); + +export type MetricsK8sAnomalyHit = rt.TypeOf; + +export const metricsK8sAnomaliesResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.type({ + hits: rt.type({ + hits: rt.array(metricsK8sAnomalyHitRT), + }), + }), +]); + +export type MetricsK8sAnomaliesResponseRT = rt.TypeOf; + +const parsePaginationCursor = (sort: Sort, pagination: Pagination) => { + const { cursor } = pagination; + const { direction } = sort; + + if (!cursor) { + return { querySortDirection: direction, queryCursor: undefined }; + } + + // We will always use ES's search_after to paginate, to mimic "search_before" behaviour we + // need to reverse the user's chosen search direction for the ES query. + if ('searchBefore' in cursor) { + return { + querySortDirection: direction === 'desc' ? 'asc' : 'desc', + queryCursor: cursor.searchBefore, + }; + } else { + return { querySortDirection: direction, queryCursor: cursor.searchAfter }; + } +}; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/ml_jobs.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/ml_jobs.ts new file mode 100644 index 0000000000000..ee4ccbfaeb5a7 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/ml_jobs.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const createMlJobsQuery = (jobIds: string[]) => ({ + method: 'GET', + path: `/_ml/anomaly_detectors/${jobIds.join(',')}`, + query: { + allow_no_jobs: true, + }, +}); + +export const mlJobRT = rt.type({ + job_id: rt.string, + custom_settings: rt.unknown, +}); + +export const mlJobsResponseRT = rt.type({ + jobs: rt.array(mlJobRT), +}); diff --git a/x-pack/plugins/infra/server/lib/log_analysis/common.ts b/x-pack/plugins/infra/server/lib/log_analysis/common.ts index 4d2be94c7cd62..7e4a714a47d1f 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/common.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/common.ts @@ -36,7 +36,7 @@ export async function fetchMlJob(mlAnomalyDetectors: MlAnomalyDetectors, jobId: }; } -const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000; +export const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000; // Finds datasets related to ML job ids export async function getLogEntryDatasets( diff --git a/x-pack/plugins/infra/server/lib/log_analysis/index.ts b/x-pack/plugins/infra/server/lib/log_analysis/index.ts index c9a176be0a28f..bb571a8edf39b 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/index.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/index.ts @@ -6,5 +6,6 @@ export * from './errors'; export * from './log_entry_categories_analysis'; +export * from './log_entry_categories_datasets_stats'; export * from './log_entry_rate_analysis'; export * from './log_entry_anomalies'; diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_datasets_stats.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_datasets_stats.ts new file mode 100644 index 0000000000000..ec5f3c88dff2a --- /dev/null +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_datasets_stats.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { startTracingSpan } from '../../../common/performance_tracing'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import type { MlAnomalyDetectors, MlSystem } from '../../types'; +import { COMPOSITE_AGGREGATION_BATCH_SIZE } from './common'; +import { + CompositeDatasetKey, + createLatestLogEntryCategoriesDatasetsStatsQuery, + latestLogEntryCategoriesDatasetsStatsResponseRT, + LogEntryCategoryDatasetStatsBucket, +} from './queries/latest_log_entry_categories_datasets_stats'; + +export async function getLatestLogEntriesCategoriesDatasetsStats( + context: { + infra: { + mlAnomalyDetectors: MlAnomalyDetectors; + mlSystem: MlSystem; + }; + }, + jobIds: string[], + startTime: number, + endTime: number, + includeCategorizerStatuses: Array<'ok' | 'warn'> = [] +) { + const finalizeLogEntryCategoriesDatasetsStats = startTracingSpan('get categories datasets stats'); + + let latestLogEntryCategoriesDatasetsStatsBuckets: LogEntryCategoryDatasetStatsBucket[] = []; + let afterLatestBatchKey: CompositeDatasetKey | undefined; + + while (true) { + const latestLogEntryCategoriesDatasetsStatsResponse = await context.infra.mlSystem.mlAnomalySearch( + createLatestLogEntryCategoriesDatasetsStatsQuery( + jobIds, + startTime, + endTime, + COMPOSITE_AGGREGATION_BATCH_SIZE, + afterLatestBatchKey + ) + ); + + const { after_key: afterKey, buckets: latestBatchBuckets = [] } = + decodeOrThrow(latestLogEntryCategoriesDatasetsStatsResponseRT)( + latestLogEntryCategoriesDatasetsStatsResponse + ).aggregations?.dataset_composite_terms ?? {}; + + const latestIncludedBatchBuckets = + includeCategorizerStatuses.length > 0 + ? latestBatchBuckets.filter((bucket) => + bucket.categorizer_stats_top_hits.hits.hits.some((hit) => + includeCategorizerStatuses.includes(hit._source.categorization_status) + ) + ) + : latestBatchBuckets; + + latestLogEntryCategoriesDatasetsStatsBuckets = [ + ...latestLogEntryCategoriesDatasetsStatsBuckets, + ...latestIncludedBatchBuckets, + ]; + + afterLatestBatchKey = afterKey; + if (afterKey == null || latestBatchBuckets.length < COMPOSITE_AGGREGATION_BATCH_SIZE) { + break; + } + } + + const logEntryCategoriesDatasetsStatsSpan = finalizeLogEntryCategoriesDatasetsStats(); + + return { + data: latestLogEntryCategoriesDatasetsStatsBuckets.map((bucket) => { + const latestHitSource = bucket.categorizer_stats_top_hits.hits.hits[0]._source; + + return { + categorization_status: latestHitSource.categorization_status, + categorized_doc_count: latestHitSource.categorized_doc_count, + dataset: bucket.key.dataset ?? '', + dead_category_count: latestHitSource.dead_category_count, + failed_category_count: latestHitSource.failed_category_count, + frequent_category_count: latestHitSource.frequent_category_count, + job_id: latestHitSource.job_id, + log_time: latestHitSource.log_time, + rare_category_count: latestHitSource.rare_category_count, + total_category_count: latestHitSource.total_category_count, + }; + }), + timing: { + spans: [logEntryCategoriesDatasetsStatsSpan], + }, + }; +} diff --git a/x-pack/plugins/infra/server/lib/log_analysis/queries/common.ts b/x-pack/plugins/infra/server/lib/log_analysis/queries/common.ts index 63e39ef022392..bb1a1969e99eb 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/queries/common.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/queries/common.ts @@ -40,7 +40,20 @@ export const createTimeRangeFilters = (startTime: number, endTime: number) => [ }, ]; -export const createResultTypeFilters = (resultTypes: Array<'model_plot' | 'record'>) => [ +export const createLogTimeRangeFilters = (startTime: number, endTime: number) => [ + { + range: { + log_time: { + gte: startTime, + lte: endTime, + }, + }, + }, +]; + +export const createResultTypeFilters = ( + resultTypes: Array<'categorizer_stats' | 'model_plot' | 'record'> +) => [ { terms: { result_type: resultTypes, diff --git a/x-pack/plugins/infra/server/lib/log_analysis/queries/latest_log_entry_categories_datasets_stats.ts b/x-pack/plugins/infra/server/lib/log_analysis/queries/latest_log_entry_categories_datasets_stats.ts new file mode 100644 index 0000000000000..b9224e8125a48 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/log_analysis/queries/latest_log_entry_categories_datasets_stats.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { + createJobIdsFilters, + createResultTypeFilters, + defaultRequestParameters, + createLogTimeRangeFilters, +} from './common'; + +export const createLatestLogEntryCategoriesDatasetsStatsQuery = ( + logEntryCategoriesJobIds: string[], + startTime: number, + endTime: number, + size: number, + afterKey?: CompositeDatasetKey +) => ({ + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: [ + ...createJobIdsFilters(logEntryCategoriesJobIds), + ...createResultTypeFilters(['categorizer_stats']), + ...createLogTimeRangeFilters(startTime, endTime), + ], + }, + }, + aggregations: { + dataset_composite_terms: { + composite: { + after: afterKey, + size, + sources: [ + { + dataset: { + terms: { + field: 'partition_field_value', + missing_bucket: true, + }, + }, + }, + ], + }, + aggs: { + categorizer_stats_top_hits: { + top_hits: { + size: 1, + sort: [ + { + log_time: 'desc', + }, + ], + _source: [ + 'categorization_status', + 'categorized_doc_count', + 'dead_category_count', + 'failed_category_count', + 'frequent_category_count', + 'job_id', + 'log_time', + 'rare_category_count', + 'total_category_count', + ], + }, + }, + }, + }, + }, + }, + size: 0, +}); + +export const logEntryCategoryStatusRT = rt.keyof({ + ok: null, + warn: null, +}); + +export const logEntryCategorizerStatsHitRT = rt.type({ + _source: rt.type({ + categorization_status: logEntryCategoryStatusRT, + categorized_doc_count: rt.number, + dead_category_count: rt.number, + failed_category_count: rt.number, + frequent_category_count: rt.number, + job_id: rt.string, + log_time: rt.number, + rare_category_count: rt.number, + total_category_count: rt.number, + }), +}); + +export type LogEntryCategorizerStatsHit = rt.TypeOf; + +const compositeDatasetKeyRT = rt.type({ + dataset: rt.union([rt.string, rt.null]), +}); + +export type CompositeDatasetKey = rt.TypeOf; + +const logEntryCategoryDatasetStatsBucketRT = rt.type({ + key: compositeDatasetKeyRT, + categorizer_stats_top_hits: rt.type({ + hits: rt.type({ + hits: rt.array(logEntryCategorizerStatsHitRT), + }), + }), +}); + +export type LogEntryCategoryDatasetStatsBucket = rt.TypeOf< + typeof logEntryCategoryDatasetStatsBucketRT +>; + +export const latestLogEntryCategoriesDatasetsStatsResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.partial({ + aggregations: rt.type({ + dataset_composite_terms: rt.type({ + after_key: compositeDatasetKeyRT, + buckets: rt.array(logEntryCategoryDatasetStatsBucketRT), + }), + }), + }), +]); + +export type LatestLogEntryCategoriesDatasetsStatsResponse = rt.TypeOf< + typeof latestLogEntryCategoriesDatasetsStatsResponseRT +>; diff --git a/x-pack/legacy/server/lib/check_license/index.js b/x-pack/plugins/infra/server/routes/infra_ml/index.ts similarity index 83% rename from x-pack/legacy/server/lib/check_license/index.js rename to x-pack/plugins/infra/server/routes/infra_ml/index.ts index f2c070fd44b6e..38684cb22e237 100644 --- a/x-pack/legacy/server/lib/check_license/index.js +++ b/x-pack/plugins/infra/server/routes/infra_ml/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { checkLicense } from './check_license'; +export * from './results'; diff --git a/x-pack/legacy/server/lib/constants/index.ts b/x-pack/plugins/infra/server/routes/infra_ml/results/index.ts similarity index 74% rename from x-pack/legacy/server/lib/constants/index.ts rename to x-pack/plugins/infra/server/routes/infra_ml/results/index.ts index 2378aca824042..82e30291faa20 100644 --- a/x-pack/legacy/server/lib/constants/index.ts +++ b/x-pack/plugins/infra/server/routes/infra_ml/results/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS } from './xpack_info'; +export * from './metrics_hosts_anomalies'; +export * from './metrics_k8s_anomalies'; diff --git a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts new file mode 100644 index 0000000000000..29122ae159cdc --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { InfraBackendLibs } from '../../../lib/infra_types'; +import { + INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, + getMetricsHostsAnomaliesSuccessReponsePayloadRT, + getMetricsHostsAnomaliesRequestPayloadRT, + GetMetricsHostsAnomaliesRequestPayload, + Sort, + Pagination, +} from '../../../../common/http_api/infra_ml'; +import { createValidationFunction } from '../../../../common/runtime_types'; +import { assertHasInfraMlPlugins } from '../../../utils/request_context'; + +import { isMlPrivilegesError } from '../../../lib/infra_ml/errors'; +import { getMetricsHostsAnomalies } from '../../../lib/infra_ml'; + +export const initGetHostsAnomaliesRoute = ({ framework }: InfraBackendLibs) => { + framework.registerRoute( + { + method: 'post', + path: INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, + validate: { + body: createValidationFunction(getMetricsHostsAnomaliesRequestPayloadRT), + }, + }, + framework.router.handleLegacyErrors(async (requestContext, request, response) => { + const { + data: { + sourceId, + timeRange: { startTime, endTime }, + sort: sortParam, + pagination: paginationParam, + }, + } = request.body; + + const { sort, pagination } = getSortAndPagination(sortParam, paginationParam); + + try { + assertHasInfraMlPlugins(requestContext); + + const { + data: anomalies, + paginationCursors, + hasMoreEntries, + timing, + } = await getMetricsHostsAnomalies( + requestContext, + sourceId, + startTime, + endTime, + sort, + pagination + ); + + // console.log('---- anomalies', anomalies); + + return response.ok({ + body: getMetricsHostsAnomaliesSuccessReponsePayloadRT.encode({ + data: { + anomalies, + hasMoreEntries, + paginationCursors, + }, + timing, + }), + }); + } catch (error) { + if (Boom.isBoom(error)) { + throw error; + } + + if (isMlPrivilegesError(error)) { + return response.customError({ + statusCode: 403, + body: { + message: error.message, + }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + }) + ); +}; + +const getSortAndPagination = ( + sort: Partial = {}, + pagination: Partial = {} +): { + sort: Sort; + pagination: Pagination; +} => { + const sortDefaults = { + field: 'anomalyScore' as const, + direction: 'desc' as const, + }; + + const sortWithDefaults = { + ...sortDefaults, + ...sort, + }; + + const paginationDefaults = { + pageSize: 50, + }; + + const paginationWithDefaults = { + ...paginationDefaults, + ...pagination, + }; + + return { sort: sortWithDefaults, pagination: paginationWithDefaults }; +}; diff --git a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts new file mode 100644 index 0000000000000..5260c55836c59 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { InfraBackendLibs } from '../../../lib/infra_types'; +import { + INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, + getMetricsK8sAnomaliesSuccessReponsePayloadRT, + getMetricsK8sAnomaliesRequestPayloadRT, + GetMetricsK8sAnomaliesRequestPayload, + Sort, + Pagination, +} from '../../../../common/http_api/infra_ml'; +import { createValidationFunction } from '../../../../common/runtime_types'; +import { assertHasInfraMlPlugins } from '../../../utils/request_context'; +import { getMetricK8sAnomalies } from '../../../lib/infra_ml'; +import { isMlPrivilegesError } from '../../../lib/infra_ml/errors'; + +export const initGetK8sAnomaliesRoute = ({ framework }: InfraBackendLibs) => { + framework.registerRoute( + { + method: 'post', + path: INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, + validate: { + body: createValidationFunction(getMetricsK8sAnomaliesRequestPayloadRT), + }, + }, + framework.router.handleLegacyErrors(async (requestContext, request, response) => { + const { + data: { + sourceId, + timeRange: { startTime, endTime }, + sort: sortParam, + pagination: paginationParam, + }, + } = request.body; + + const { sort, pagination } = getSortAndPagination(sortParam, paginationParam); + + try { + assertHasInfraMlPlugins(requestContext); + + const { + data: anomalies, + paginationCursors, + hasMoreEntries, + timing, + } = await getMetricK8sAnomalies( + requestContext, + sourceId, + startTime, + endTime, + sort, + pagination + ); + + return response.ok({ + body: getMetricsK8sAnomaliesSuccessReponsePayloadRT.encode({ + data: { + anomalies, + hasMoreEntries, + paginationCursors, + }, + timing, + }), + }); + } catch (error) { + if (Boom.isBoom(error)) { + throw error; + } + + if (isMlPrivilegesError(error)) { + return response.customError({ + statusCode: 403, + body: { + message: error.message, + }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + }) + ); +}; + +const getSortAndPagination = ( + sort: Partial = {}, + pagination: Partial = {} +): { + sort: Sort; + pagination: Pagination; +} => { + const sortDefaults = { + field: 'anomalyScore' as const, + direction: 'desc' as const, + }; + + const sortWithDefaults = { + ...sortDefaults, + ...sort, + }; + + const paginationDefaults = { + pageSize: 50, + }; + + const paginationWithDefaults = { + ...paginationDefaults, + ...pagination, + }; + + return { sort: sortWithDefaults, pagination: paginationWithDefaults }; +}; diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/index.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/index.ts index a01042616a872..e696477253823 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/index.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/index.ts @@ -6,6 +6,7 @@ export * from './log_entry_categories'; export * from './log_entry_category_datasets'; +export * from './log_entry_category_datasets_stats'; export * from './log_entry_category_examples'; export * from './log_entry_rate'; export * from './log_entry_examples'; diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets_stats.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets_stats.ts new file mode 100644 index 0000000000000..8414fc2062ae9 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets_stats.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { + getLatestLogEntryCategoryDatasetsStatsRequestPayloadRT, + getLatestLogEntryCategoryDatasetsStatsSuccessResponsePayloadRT, + LOG_ANALYSIS_GET_LATEST_LOG_ENTRY_CATEGORY_DATASETS_STATS_PATH, +} from '../../../../common/http_api/log_analysis'; +import { createValidationFunction } from '../../../../common/runtime_types'; +import type { InfraBackendLibs } from '../../../lib/infra_types'; +import { getLatestLogEntriesCategoriesDatasetsStats } from '../../../lib/log_analysis'; +import { isMlPrivilegesError } from '../../../lib/log_analysis/errors'; +import { assertHasInfraMlPlugins } from '../../../utils/request_context'; + +export const initGetLogEntryCategoryDatasetsStatsRoute = ({ framework }: InfraBackendLibs) => { + framework.registerRoute( + { + method: 'post', + path: LOG_ANALYSIS_GET_LATEST_LOG_ENTRY_CATEGORY_DATASETS_STATS_PATH, + validate: { + body: createValidationFunction(getLatestLogEntryCategoryDatasetsStatsRequestPayloadRT), + }, + }, + framework.router.handleLegacyErrors(async (requestContext, request, response) => { + const { + data: { + jobIds, + timeRange: { startTime, endTime }, + includeCategorizerStatuses, + }, + } = request.body; + + try { + assertHasInfraMlPlugins(requestContext); + + const { data: datasetStats, timing } = await getLatestLogEntriesCategoriesDatasetsStats( + requestContext, + jobIds, + startTime, + endTime, + includeCategorizerStatuses + ); + + return response.ok({ + body: getLatestLogEntryCategoryDatasetsStatsSuccessResponsePayloadRT.encode({ + data: { + datasetStats, + }, + timing, + }), + }); + } catch (error) { + if (Boom.isBoom(error)) { + throw error; + } + + if (isMlPrivilegesError(error)) { + return response.customError({ + statusCode: 403, + body: { + message: error.message, + }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + }) + ); +}; diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts index 700f4ef39bb66..b18b45f4935d2 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { findInventoryFields } from '../../../../common/inventory_models'; +import { findInventoryFields, findInventoryModel } from '../../../../common/inventory_models'; import { MetricsAPIRequest, SnapshotRequest } from '../../../../common/http_api'; import { ESSearchClient } from '../../../lib/metrics/types'; import { InfraSource } from '../../../lib/sources'; @@ -34,7 +34,7 @@ export const transformRequestToMetricsAPIRequest = async ( interval: timeRangeWithIntervalApplied.interval, }, metrics: transformSnapshotMetricsToMetricsAPIMetrics(snapshotRequest), - limit: snapshotRequest.overrideCompositeSize ? snapshotRequest.overrideCompositeSize : 10, + limit: snapshotRequest.overrideCompositeSize ? snapshotRequest.overrideCompositeSize : 5, alignDataToEnd: true, }; @@ -52,12 +52,19 @@ export const transformRequestToMetricsAPIRequest = async ( filters.push({ term: { 'cloud.region': snapshotRequest.region } }); } + const inventoryModel = findInventoryModel(snapshotRequest.nodeType); + if (inventoryModel && inventoryModel.nodeFilter) { + inventoryModel.nodeFilter?.forEach((f) => filters.push(f)); + } + const inventoryFields = findInventoryFields( snapshotRequest.nodeType, source.configuration.fields ); - const groupBy = snapshotRequest.groupBy.map((g) => g.field).filter(Boolean) as string[]; - metricsApiRequest.groupBy = [...groupBy, inventoryFields.id]; + if (snapshotRequest.groupBy) { + const groupBy = snapshotRequest.groupBy.map((g) => g.field).filter(Boolean) as string[]; + metricsApiRequest.groupBy = [...groupBy, inventoryFields.id]; + } const metaAggregation = { id: META_KEY, diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts index 378a6c6c12159..69672dfb9ec6c 100644 --- a/x-pack/plugins/ingest_manager/common/constants/routes.ts +++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts @@ -86,8 +86,11 @@ export const AGENT_API_ROUTES = { ACTIONS_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/actions`, ENROLL_PATTERN: `${FLEET_API_ROOT}/agents/enroll`, UNENROLL_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/unenroll`, + BULK_UNENROLL_PATTERN: `${FLEET_API_ROOT}/agents/bulk_unenroll`, REASSIGN_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/reassign`, + BULK_REASSIGN_PATTERN: `${FLEET_API_ROOT}/agents/bulk_reassign`, STATUS_PATTERN: `${FLEET_API_ROOT}/agent-status`, + UPGRADE_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/upgrade`, }; export const ENROLLMENT_API_KEY_ROUTES = { diff --git a/x-pack/plugins/ingest_manager/common/services/agent_status.ts b/x-pack/plugins/ingest_manager/common/services/agent_status.ts index fe4e094e1bb22..70f4d7f9344f9 100644 --- a/x-pack/plugins/ingest_manager/common/services/agent_status.ts +++ b/x-pack/plugins/ingest_manager/common/services/agent_status.ts @@ -19,6 +19,9 @@ export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentSta if (!agent.last_checkin) { return 'enrolling'; } + if (agent.upgrade_started_at && !agent.upgraded_at) { + return 'upgrading'; + } const msLastCheckIn = new Date(lastCheckIn || 0).getTime(); const msSinceLastCheckIn = new Date().getTime() - msLastCheckIn; diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/ingest_manager/common/services/index.ts index 46a1c65872d1b..4bffa01ad5ee2 100644 --- a/x-pack/plugins/ingest_manager/common/services/index.ts +++ b/x-pack/plugins/ingest_manager/common/services/index.ts @@ -12,3 +12,4 @@ export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limite export { decodeCloudId } from './decode_cloud_id'; export { isValidNamespace } from './is_valid_namespace'; export { isDiffPathProtocol } from './is_diff_path_protocol'; +export { LicenseService } from './license'; diff --git a/x-pack/plugins/ingest_manager/common/services/license.ts b/x-pack/plugins/ingest_manager/common/services/license.ts new file mode 100644 index 0000000000000..6d9b20a8456c0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/license.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; + * you may not use this file except in compliance with the Elastic License. + */ +import { Observable, Subscription } from 'rxjs'; +import { ILicense } from '../../../licensing/common/types'; + +// Generic license service class that works with the license observable +// Both server and client plugins instancates a singleton version of this class +export class LicenseService { + private observable: Observable | null = null; + private subscription: Subscription | null = null; + private licenseInformation: ILicense | null = null; + + private updateInformation(licenseInformation: ILicense) { + this.licenseInformation = licenseInformation; + } + + public start(license$: Observable) { + this.observable = license$; + this.subscription = this.observable.subscribe(this.updateInformation.bind(this)); + } + + public stop() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + public getLicenseInformation() { + return this.licenseInformation; + } + + public getLicenseInformation$() { + return this.observable; + } + + public isGoldPlus() { + return ( + this.licenseInformation?.isAvailable && + this.licenseInformation?.isActive && + this.licenseInformation?.hasAtLeast('gold') + ); + } +} diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts index ec7c0ee850834..3c3534926908a 100644 --- a/x-pack/plugins/ingest_manager/common/services/routes.ts +++ b/x-pack/plugins/ingest_manager/common/services/routes.ts @@ -131,8 +131,10 @@ export const agentRouteService = { getEventsPath: (agentId: string) => AGENT_API_ROUTES.EVENTS_PATTERN.replace('{agentId}', agentId), getUnenrollPath: (agentId: string) => AGENT_API_ROUTES.UNENROLL_PATTERN.replace('{agentId}', agentId), + getBulkUnenrollPath: () => AGENT_API_ROUTES.BULK_UNENROLL_PATTERN, getReassignPath: (agentId: string) => AGENT_API_ROUTES.REASSIGN_PATTERN.replace('{agentId}', agentId), + getBulkReassignPath: () => AGENT_API_ROUTES.BULK_REASSIGN_PATTERN, getListPath: () => AGENT_API_ROUTES.LIST_PATTERN, getStatusPath: () => AGENT_API_ROUTES.STATUS_PATTERN, }; diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent.ts b/x-pack/plugins/ingest_manager/common/types/models/agent.ts index a204373fe2e56..7110fd4ce52ea 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/agent.ts @@ -19,10 +19,10 @@ export type AgentStatus = | 'warning' | 'enrolling' | 'unenrolling' + | 'upgrading' | 'degraded'; -export type AgentActionType = 'CONFIG_CHANGE' | 'UNENROLL'; - +export type AgentActionType = 'CONFIG_CHANGE' | 'UNENROLL' | 'UPGRADE'; export interface NewAgentAction { type: AgentActionType; data?: any; @@ -65,7 +65,6 @@ export type AgentPolicyActionSOAttributes = CommonAgentActionSOAttributes & { policy_id: string; policy_revision: number; }; - export type BaseAgentActionSOAttributes = AgentActionSOAttributes | AgentPolicyActionSOAttributes; export interface NewAgentEvent { @@ -110,6 +109,8 @@ interface AgentBase { enrolled_at: string; unenrolled_at?: string; unenrollment_started_at?: string; + upgraded_at?: string; + upgrade_started_at?: string; shared_id?: string; access_api_key_id?: string; default_api_key?: string; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts index 54cdeade3764e..ab4c372c4e1d6 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts @@ -26,6 +26,7 @@ export interface GetAgentsRequest { export interface GetAgentsResponse { list: Agent[]; total: number; + totalInactive: number; page: number; perPage: number; } @@ -104,11 +105,31 @@ export interface PostAgentUnenrollRequest { params: { agentId: string; }; + body: { + force?: boolean; + }; } // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PostAgentUnenrollResponse {} +export interface PostAgentUpgradeRequest { + params: { + agentId: string; + }; +} +export interface PostBulkAgentUnenrollRequest { + body: { + agents: string[] | string; + force?: boolean; + }; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PostAgentUpgradeResponse {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PostBulkAgentUnenrollResponse {} + export interface PutAgentReassignRequest { params: { agentId: string; @@ -119,6 +140,20 @@ export interface PutAgentReassignRequest { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PutAgentReassignResponse {} +export interface PostBulkAgentReassignRequest { + body: { + policy_id: string; + agents: string[] | string; + }; +} + +export interface PostBulkAgentReassignResponse { + [key: string]: { + success: boolean; + error?: Error; + }; +} + export interface GetOneAgentEventsRequest { params: { agentId: string; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts index 36b7d412bf276..64434e163f043 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts @@ -8,6 +8,7 @@ export { useCapabilities } from './use_capabilities'; export { useCore } from './use_core'; export { useConfig, ConfigContext } from './use_config'; export { useSetupDeps, useStartDeps, DepsContext } from './use_deps'; +export { licenseService, useLicense } from './use_license'; export { useBreadcrumbs } from './use_breadcrumbs'; export { useLink } from './use_link'; export { useKibanaLink } from './use_kibana_link'; diff --git a/x-pack/index.js b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_license.ts similarity index 61% rename from x-pack/index.js rename to x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_license.ts index cb68004c26d65..411a6d6f2168f 100644 --- a/x-pack/index.js +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_license.ts @@ -3,9 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { LicenseService } from '../services'; -import { xpackMain } from './legacy/plugins/xpack_main'; +export const licenseService = new LicenseService(); -module.exports = function (kibana) { - return [xpackMain(kibana)]; -}; +export function useLicense() { + return licenseService; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts index cad1791af41be..41967fd068e0b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts @@ -10,8 +10,14 @@ import { GetOneAgentResponse, GetOneAgentEventsResponse, GetOneAgentEventsRequest, + PostAgentUnenrollRequest, + PostBulkAgentUnenrollRequest, + PostBulkAgentUnenrollResponse, + PostAgentUnenrollResponse, PutAgentReassignRequest, PutAgentReassignResponse, + PostBulkAgentReassignRequest, + PostBulkAgentReassignResponse, GetAgentsRequest, GetAgentsResponse, GetAgentStatusRequest, @@ -83,3 +89,40 @@ export function sendPutAgentReassign( ...options, }); } + +export function sendPostBulkAgentReassign( + body: PostBulkAgentReassignRequest['body'], + options?: RequestOptions +) { + return sendRequest({ + method: 'post', + path: agentRouteService.getBulkReassignPath(), + body, + ...options, + }); +} + +export function sendPostAgentUnenroll( + agentId: string, + body: PostAgentUnenrollRequest['body'], + options?: RequestOptions +) { + return sendRequest({ + path: agentRouteService.getUnenrollPath(agentId), + method: 'post', + body, + ...options, + }); +} + +export function sendPostBulkAgentUnenroll( + body: PostBulkAgentUnenrollRequest['body'], + options?: RequestOptions +) { + return sendRequest({ + path: agentRouteService.getBulkUnenrollPath(), + method: 'post', + body, + ...options, + }); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx index 5520a50463db4..0bef3c20ddd1a 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import styled from 'styled-components'; import { EuiErrorBoundary, EuiPanel, EuiEmptyPrompt, EuiCode } from '@elastic/eui'; import { CoreStart, AppMountParameters } from 'src/core/public'; -import { EuiThemeProvider } from '../../../../../legacy/common/eui_styled_components'; +import { EuiThemeProvider } from '../../../../xpack_legacy/common'; import { IngestManagerSetupDeps, IngestManagerConfigType, @@ -22,9 +22,16 @@ import { PAGE_ROUTING_PATHS } from './constants'; import { DefaultLayout, WithoutHeaderLayout } from './layouts'; import { Loading, Error } from './components'; import { IngestManagerOverview, EPMApp, AgentPolicyApp, FleetApp, DataStreamApp } from './sections'; -import { DepsContext, ConfigContext, useConfig } from './hooks'; +import { + DepsContext, + ConfigContext, + useConfig, + useCore, + sendSetup, + sendGetPermissionsCheck, + licenseService, +} from './hooks'; import { PackageInstallProvider } from './sections/epm/hooks'; -import { useCore, sendSetup, sendGetPermissionsCheck } from './hooks'; import { FleetStatusProvider } from './hooks/use_fleet_status'; import './index.scss'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; @@ -279,4 +286,5 @@ export function renderApp( export const teardownIngestManager = (coreStart: CoreStart) => { coreStart.chrome.docTitle.reset(); coreStart.chrome.setBreadcrumbs([]); + licenseService.stop(); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx index 636ff7a5ff989..ea5dcce8c05bb 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; import { useCapabilities } from '../../../../hooks'; import { ContextMenuActions } from '../../../../components'; -import { AgentUnenrollProvider, AgentReassignAgentPolicyFlyout } from '../../components'; +import { AgentUnenrollAgentModal, AgentReassignAgentPolicyFlyout } from '../../components'; import { useAgentRefresh } from '../hooks'; export const AgentDetailsActionMenu: React.FunctionComponent<{ @@ -20,6 +20,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ const hasWriteCapabilites = useCapabilities().write; const refreshAgent = useAgentRefresh(); const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(assignFlyoutOpenByDefault); + const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false); const isUnenrolling = agent.status === 'unenrolling'; const onClose = useMemo(() => { @@ -34,7 +35,20 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ <> {isReassignFlyoutOpen && ( - + + + )} + {isUnenrollModalOpen && ( + + { + setIsUnenrollModalOpen(false); + refreshAgent(); + }} + useForceUnenroll={isUnenrolling} + /> )} , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, refreshAgent); - }} - > - {isUnenrolling ? ( - - ) : ( - - )} - + { + setIsUnenrollModalOpen(true); + }} + > + {isUnenrolling ? ( + + ) : ( + )} - , + , ]} /> diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/bulk_actions.tsx new file mode 100644 index 0000000000000..25684c9faf594 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/bulk_actions.tsx @@ -0,0 +1,225 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiPopover, + EuiContextMenu, + EuiButtonEmpty, + EuiIcon, + EuiPortal, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Agent } from '../../../../types'; +import { AgentReassignAgentPolicyFlyout, AgentUnenrollAgentModal } from '../../components'; + +const Divider = styled.div` + width: 0; + height: ${(props) => props.theme.eui.euiSizeL}; + border-left: ${(props) => props.theme.eui.euiBorderThin}; +`; + +const FlexItem = styled(EuiFlexItem)` + height: ${(props) => props.theme.eui.euiSizeL}; +`; + +const Button = styled(EuiButtonEmpty)` + .euiButtonEmpty__text { + font-size: ${(props) => props.theme.eui.euiFontSizeXS}; + } +`; + +export type SelectionMode = 'manual' | 'query'; + +export const AgentBulkActions: React.FunctionComponent<{ + totalAgents: number; + totalInactiveAgents: number; + selectableAgents: number; + selectionMode: SelectionMode; + setSelectionMode: (mode: SelectionMode) => void; + currentQuery: string; + selectedAgents: Agent[]; + setSelectedAgents: (agents: Agent[]) => void; + refreshAgents: () => void; +}> = ({ + totalAgents, + totalInactiveAgents, + selectableAgents, + selectionMode, + setSelectionMode, + currentQuery, + selectedAgents, + setSelectedAgents, + refreshAgents, +}) => { + // Bulk actions menu states + const [isMenuOpen, setIsMenuOpen] = useState(false); + const closeMenu = () => setIsMenuOpen(false); + const openMenu = () => setIsMenuOpen(true); + + // Actions states + const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); + const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false); + + // Check if user is working with only inactive agents + const atLeastOneActiveAgentSelected = + selectionMode === 'manual' + ? !!selectedAgents.find((agent) => agent.active) + : totalAgents > totalInactiveAgents; + + const panels = [ + { + id: 0, + items: [ + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setIsReassignFlyoutOpen(true); + }, + }, + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setIsUnenrollModalOpen(true); + }, + }, + { + name: ( + + ), + icon: , + onClick: () => { + closeMenu(); + setSelectionMode('manual'); + setSelectedAgents([]); + }, + }, + ], + }, + ]; + + return ( + <> + {isReassignFlyoutOpen && ( + + { + setIsReassignFlyoutOpen(false); + refreshAgents(); + }} + /> + + )} + {isUnenrollModalOpen && ( + + { + setIsUnenrollModalOpen(false); + refreshAgents(); + }} + /> + + )} + + + + + + + {(selectionMode === 'manual' && selectedAgents.length) || + (selectionMode === 'query' && totalAgents > 0) ? ( + <> + + + + + + + + } + isOpen={isMenuOpen} + closePopover={closeMenu} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + + {selectionMode === 'manual' && + selectedAgents.length === selectableAgents && + selectableAgents < totalAgents ? ( + + + + ) : null} + + ) : ( + + )} + + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx index 46f7ffb85b21f..0bc463ce98590 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useMemo, useCallback } from 'react'; +import React, { useState, useMemo, useCallback, useRef } from 'react'; import { EuiBasicTable, EuiButton, @@ -20,6 +20,7 @@ import { EuiContextMenuItem, EuiIcon, EuiPortal, + EuiHorizontalRule, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; @@ -33,11 +34,17 @@ import { useUrlParams, useLink, useBreadcrumbs, + useLicense, } from '../../../hooks'; import { SearchBar, ContextMenuActions } from '../../../components'; import { AgentStatusKueryHelper } from '../../../services'; import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; -import { AgentReassignAgentPolicyFlyout, AgentHealth, AgentUnenrollProvider } from '../components'; +import { + AgentReassignAgentPolicyFlyout, + AgentHealth, + AgentUnenrollAgentModal, +} from '../components'; +import { AgentBulkActions, SelectionMode } from './components/bulk_actions'; const REFRESH_INTERVAL_MS = 5000; @@ -63,72 +70,68 @@ const statusFilters = [ }, ] as Array<{ label: string; status: string }>; -const RowActions = React.memo<{ agent: Agent; onReassignClick: () => void; refresh: () => void }>( - ({ agent, refresh, onReassignClick }) => { - const { getHref } = useLink(); - const hasWriteCapabilites = useCapabilities().write; +const RowActions = React.memo<{ + agent: Agent; + refresh: () => void; + onReassignClick: () => void; + onUnenrollClick: () => void; +}>(({ agent, refresh, onReassignClick, onUnenrollClick }) => { + const { getHref } = useLink(); + const hasWriteCapabilites = useCapabilities().write; - const isUnenrolling = agent.status === 'unenrolling'; - const [isMenuOpen, setIsMenuOpen] = useState(false); - return ( - setIsMenuOpen(isOpen)} - items={[ - + const isUnenrolling = agent.status === 'unenrolling'; + const [isMenuOpen, setIsMenuOpen] = useState(false); + return ( + setIsMenuOpen(isOpen)} + items={[ + + + , + { + onReassignClick(); + }} + disabled={!agent.active} + key="reassignPolicy" + > + + , + { + onUnenrollClick(); + }} + > + {isUnenrolling ? ( - , - { - onReassignClick(); - }} - disabled={!agent.active} - key="reassignPolicy" - > + ) : ( - , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, () => { - refresh(); - setIsMenuOpen(false); - }); - }} - > - {isUnenrolling ? ( - - ) : ( - - )} - - )} - , - ]} - /> - ); - } -); + )} + , + ]} + /> + ); +}); function safeMetadata(val: any) { if (typeof val !== 'string') { @@ -142,12 +145,16 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { const { getHref } = useLink(); const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || ''; const hasWriteCapabilites = useCapabilities().write; + const isGoldPlus = useLicense().isGoldPlus(); // Agent data states const [showInactive, setShowInactive] = useState(false); // Table and search states - const [search, setSearch] = useState(defaultKuery); + const [search, setSearch] = useState(defaultKuery); + const [selectionMode, setSelectionMode] = useState('manual'); + const [selectedAgents, setSelectedAgents] = useState([]); + const tableRef = useRef>(null); const { pagination, pageSizeOptions, setPagination } = usePagination(); // Policies state for filtering @@ -179,8 +186,9 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { // Agent enrollment flyout state const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); - // Agent reassignment flyout state - const [agentToReassignId, setAgentToReassignId] = useState(undefined); + // Agent actions states + const [agentToReassign, setAgentToReassign] = useState(undefined); + const [agentToUnenroll, setAgentToUnenroll] = useState(undefined); let kuery = search.trim(); if (selectedAgentPolicies.length) { @@ -229,6 +237,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { const agents = agentsRequest.data ? agentsRequest.data.list : []; const totalAgents = agentsRequest.data ? agentsRequest.data.total : 0; + const totalInactiveAgents = agentsRequest.data ? agentsRequest.data.totalInactive : 0; const { isLoading } = agentsRequest; const agentPoliciesRequest = useGetAgentPolicies({ @@ -345,7 +354,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { agentsRequest.resendRequest()} - onReassignClick={() => setAgentToReassignId(agent.id)} + onReassignClick={() => setAgentToReassign(agent)} + onUnenrollClick={() => setAgentToUnenroll(agent)} /> ); }, @@ -378,8 +388,6 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { /> ); - const agentToReassign = agentToReassignId && agents.find((a) => a.id === agentToReassignId); - return ( <> {isEnrollmentFlyoutOpen ? ( @@ -391,15 +399,30 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { {agentToReassign && ( { - setAgentToReassignId(undefined); + setAgentToReassign(undefined); agentsRequest.resendRequest(); }} /> )} - + {agentToUnenroll && ( + + { + setAgentToUnenroll(undefined); + agentsRequest.resendRequest(); + }} + useForceUnenroll={agentToUnenroll.status === 'unenrolling'} + /> + + )} + + {/* Search and filter bar */} + @@ -510,9 +533,31 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { - + + {/* Agent total and bulk actions */} + agent.active).length || 0} + selectionMode={selectionMode} + setSelectionMode={setSelectionMode} + currentQuery={kuery} + selectedAgents={selectedAgents} + setSelectedAgents={(newAgents: Agent[]) => { + if (tableRef?.current) { + tableRef.current.setSelection(newAgents); + setSelectionMode('manual'); + } + }} + refreshAgents={() => agentsRequest.resendRequest()} + /> + + + + {/* Agent list table */} + ref={tableRef} className="fleet__agentList__table" data-test-subj="fleetAgentListTable" loading={isLoading} @@ -551,6 +596,18 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { totalItemCount: totalAgents, pageSizeOptions, }} + isSelectable={true} + selection={ + isGoldPlus + ? { + onSelectionChange: (newAgents: Agent[]) => { + setSelectedAgents(newAgents); + setSelectionMode('manual'); + }, + selectable: (agent: Agent) => agent.active, + } + : undefined + } onChange={({ page }: { page: { index: number; size: number } }) => { const newPagination = { ...pagination, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx index 0c154bf1074c0..d3af1287c4025 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlyout, @@ -22,40 +22,55 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; -import { sendPutAgentReassign, useCore, useGetAgentPolicies } from '../../../../hooks'; +import { + sendPutAgentReassign, + sendPostBulkAgentReassign, + useCore, + useGetAgentPolicies, +} from '../../../../hooks'; import { AgentPolicyPackageBadges } from '../agent_policy_package_badges'; interface Props { onClose: () => void; - agent: Agent; + agents: Agent[] | string; } export const AgentReassignAgentPolicyFlyout: React.FunctionComponent = ({ onClose, - agent, + agents, }) => { const { notifications } = useCore(); + const isSingleAgent = Array.isArray(agents) && agents.length === 1; + const [selectedAgentPolicyId, setSelectedAgentPolicyId] = useState( - agent.policy_id + isSingleAgent ? (agents[0] as Agent).policy_id : undefined ); - const agentPoliciesRequest = useGetAgentPolicies({ page: 1, perPage: 1000, }); const agentPolicies = agentPoliciesRequest.data ? agentPoliciesRequest.data.items : []; + useEffect(() => { + if (!selectedAgentPolicyId && agentPolicies[0]) { + setSelectedAgentPolicyId(agentPolicies[0].id); + } + }, [agentPolicies, selectedAgentPolicyId]); const [isSubmitting, setIsSubmitting] = useState(false); - async function onSubmit() { try { setIsSubmitting(true); if (!selectedAgentPolicyId) { throw new Error('No selected agent policy id'); } - const res = await sendPutAgentReassign(agent.id, { - policy_id: selectedAgentPolicyId, - }); + const res = isSingleAgent + ? await sendPutAgentReassign((agents[0] as Agent).id, { + policy_id: selectedAgentPolicyId, + }) + : await sendPostBulkAgentReassign({ + policy_id: selectedAgentPolicyId, + agents: Array.isArray(agents) ? agents.map((agent) => agent.id) : agents, + }); if (res.error) { throw res.error; } @@ -91,7 +106,10 @@ export const AgentReassignAgentPolicyFlyout: React.FunctionComponent = ({ @@ -106,6 +124,7 @@ export const AgentReassignAgentPolicyFlyout: React.FunctionComponent = ({ > ({ value: agentPolicy.id, text: agentPolicy.name, @@ -134,7 +153,7 @@ export const AgentReassignAgentPolicyFlyout: React.FunctionComponent = ({ void; + agents: Agent[] | string; + agentCount: number; + useForceUnenroll?: boolean; +} + +export const AgentUnenrollAgentModal: React.FunctionComponent = ({ + onClose, + agents, + agentCount, + useForceUnenroll, +}) => { + const { notifications } = useCore(); + const [forceUnenroll, setForceUnenroll] = useState(useForceUnenroll || false); + const [isSubmitting, setIsSubmitting] = useState(false); + const isSingleAgent = Array.isArray(agents) && agents.length === 1; + + async function onSubmit() { + try { + setIsSubmitting(true); + const { error } = isSingleAgent + ? await sendPostAgentUnenroll((agents[0] as Agent).id, { + force: forceUnenroll, + }) + : await sendPostBulkAgentUnenroll({ + agents: Array.isArray(agents) ? agents.map((agent) => agent.id) : agents, + force: forceUnenroll, + }); + if (error) { + throw error; + } + setIsSubmitting(false); + if (forceUnenroll) { + const successMessage = isSingleAgent + ? i18n.translate( + 'xpack.ingestManager.unenrollAgents.successForceSingleNotificationTitle', + { defaultMessage: 'Agent unenrolled' } + ) + : i18n.translate( + 'xpack.ingestManager.unenrollAgents.successForceMultiNotificationTitle', + { defaultMessage: 'Agents unenrolled' } + ); + notifications.toasts.addSuccess(successMessage); + } else { + const successMessage = isSingleAgent + ? i18n.translate('xpack.ingestManager.unenrollAgents.successSingleNotificationTitle', { + defaultMessage: 'Unenrolling agent', + }) + : i18n.translate('xpack.ingestManager.unenrollAgents.successMultiNotificationTitle', { + defaultMessage: 'Unenrolling agents', + }); + notifications.toasts.addSuccess(successMessage); + } + onClose(); + } catch (error) { + setIsSubmitting(false); + notifications.toasts.addError(error, { + title: i18n.translate('xpack.ingestManager.unenrollAgents.fatalErrorNotificationTitle', { + defaultMessage: 'Error unenrolling {count, plural, one {agent} other {agents}}', + values: { count: agentCount }, + }), + }); + } + } + + return ( + + + ) : ( + + ) + } + onCancel={onClose} + onConfirm={onSubmit} + cancelButtonText={ + + } + confirmButtonDisabled={isSubmitting} + confirmButtonText={ + isSingleAgent ? ( + + ) : ( + + ) + } + buttonColor="danger" + > +

+ {isSingleAgent ? ( + + ) : ( + + )} +

+ + ), + }} + > + + } + checked={forceUnenroll} + onChange={(e) => setForceUnenroll(e.target.checked)} + disabled={useForceUnenroll} + /> + +
+
+ ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_provider.tsx deleted file mode 100644 index 6f1cba70bbcee..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_provider.tsx +++ /dev/null @@ -1,174 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Fragment, useRef, useState } from 'react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { useCore, sendRequest } from '../../../hooks'; -import { PostAgentUnenrollResponse } from '../../../types'; -import { agentRouteService } from '../../../services'; - -interface Props { - children: (unenrollAgents: UnenrollAgents) => React.ReactElement; - forceUnenroll?: boolean; -} - -export type UnenrollAgents = ( - agents: string[] | string, - agentsCount: number, - onSuccess?: OnSuccessCallback -) => void; - -type OnSuccessCallback = (agentsUnenrolled: string[]) => void; - -export const AgentUnenrollProvider: React.FunctionComponent = ({ - children, - forceUnenroll = false, -}) => { - const core = useCore(); - const [agents, setAgents] = useState([]); - const [agentsCount, setAgentsCount] = useState(0); - const [isModalOpen, setIsModalOpen] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const onSuccessCallback = useRef(null); - - const unenrollAgentsPrompt: UnenrollAgents = ( - agentsToUnenroll, - agentsToUnenrollCount, - onSuccess = () => undefined - ) => { - if ( - agentsToUnenroll === undefined || - // !Only supports unenrolling one agent - (Array.isArray(agentsToUnenroll) && agentsToUnenroll.length !== 1) - ) { - throw new Error('No agents specified for unenrollment'); - } - setIsModalOpen(true); - setAgents(agentsToUnenroll); - setAgentsCount(agentsToUnenrollCount); - onSuccessCallback.current = onSuccess; - }; - - const closeModal = () => { - setAgents([]); - setAgentsCount(0); - setIsLoading(false); - setIsModalOpen(false); - }; - - const unenrollAgents = async () => { - setIsLoading(true); - - try { - const agentId = agents[0]; - const { error } = await sendRequest({ - path: agentRouteService.getUnenrollPath(agentId), - method: 'post', - body: { - force: forceUnenroll, - }, - }); - - if (error) { - throw new Error(error.message); - } - - const successMessage = forceUnenroll - ? i18n.translate('xpack.ingestManager.unenrollAgents.successForceSingleNotificationTitle', { - defaultMessage: "Agent '{id}' unenrolled", - values: { id: agentId }, - }) - : i18n.translate('xpack.ingestManager.unenrollAgents.successSingleNotificationTitle', { - defaultMessage: "Unenrolling agent '{id}'", - values: { id: agentId }, - }); - core.notifications.toasts.addSuccess(successMessage); - - if (onSuccessCallback.current) { - onSuccessCallback.current([agentId]); - } - } catch (e) { - core.notifications.toasts.addDanger( - i18n.translate('xpack.ingestManager.unenrollAgents.fatalErrorNotificationTitle', { - defaultMessage: 'Error unenrolling agents', - }) - ); - } - - closeModal(); - }; - - const renderModal = () => { - if (!isModalOpen) { - return null; - } - - const unenrollByKuery = typeof agents === 'string'; - const isSingle = agentsCount === 1; - - return ( - - - ) : ( - - ) - ) : ( - - ) - } - onCancel={closeModal} - onConfirm={unenrollAgents} - cancelButtonText={ - - } - confirmButtonText={ - isLoading ? ( - - ) : ( - - ) - } - buttonColor="danger" - confirmButtonDisabled={isLoading} - /> - - ); - }; - - return ( - - {children(unenrollAgentsPrompt)} - {renderModal()} - - ); -}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx index 527f920f24365..eea4ed3b712b1 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx @@ -8,4 +8,4 @@ export * from './loading'; export * from './agent_reassign_policy_flyout'; export * from './agent_enrollment_flyout'; export * from './agent_health'; -export * from './agent_unenroll_provider'; +export * from './agent_unenroll_modal'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts index 2c9e8b84d4069..ed6ba5c891a0b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -25,4 +25,5 @@ export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage, isValidNamespace, + LicenseService, } from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 30a6742af6ea6..71a44089b8bf7 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -49,13 +49,18 @@ export { GetAgentsResponse, GetAgentsRequest, GetOneAgentResponse, + PostAgentUnenrollRequest, PostAgentUnenrollResponse, + PostBulkAgentUnenrollRequest, + PostBulkAgentUnenrollResponse, GetOneAgentEventsRequest, GetOneAgentEventsResponse, GetAgentStatusRequest, GetAgentStatusResponse, PutAgentReassignRequest, PutAgentReassignResponse, + PostBulkAgentReassignRequest, + PostBulkAgentReassignResponse, // API schemas - Enrollment API Keys GetEnrollmentAPIKeysResponse, GetEnrollmentAPIKeysRequest, diff --git a/x-pack/plugins/ingest_manager/public/plugin.ts b/x-pack/plugins/ingest_manager/public/plugin.ts index 536832cdaed64..5f7bfe865e892 100644 --- a/x-pack/plugins/ingest_manager/public/plugin.ts +++ b/x-pack/plugins/ingest_manager/public/plugin.ts @@ -23,7 +23,7 @@ import { BASE_PATH } from './applications/ingest_manager/constants'; import { IngestManagerConfigType } from '../common/types'; import { setupRouteService, appRoutesService } from '../common'; -import { setHttpClient } from './applications/ingest_manager/hooks'; +import { setHttpClient, licenseService } from './applications/ingest_manager/hooks'; import { TutorialDirectoryNotice, TutorialDirectoryHeaderLink, @@ -71,6 +71,9 @@ export class IngestManagerPlugin // Set up http client setHttpClient(core.http); + // Set up license service + licenseService.start(deps.licensing.license$); + // Register main Ingest Manager app core.application.register({ id: PLUGIN_ID, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts index 2ebb7a0667aab..fb867af513fdc 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts @@ -16,6 +16,7 @@ import { GetAgentStatusResponse, PutAgentReassignResponse, PostAgentEnrollRequest, + PostBulkAgentReassignResponse, } from '../../../common/types'; import { GetAgentsRequestSchema, @@ -26,11 +27,13 @@ import { PostAgentCheckinRequest, GetAgentStatusRequestSchema, PutAgentReassignRequestSchema, + PostBulkAgentReassignRequestSchema, } from '../../types'; +import { defaultIngestErrorHandler } from '../../errors'; +import { licenseService } from '../../services'; import * as AgentService from '../../services/agents'; import * as APIKeyService from '../../services/api_keys'; import { appContextService } from '../../services/app_context'; -import { defaultIngestErrorHandler } from '../../errors'; export const getAgentHandler: RequestHandler ({ @@ -245,6 +253,7 @@ export const getAgentsHandler: RequestHandler< status: AgentService.getAgentStatus(agent), })), total, + totalInactive, page, perPage, }; @@ -270,6 +279,47 @@ export const putAgentsReassignHandler: RequestHandler< } }; +export const postBulkAgentsReassignHandler: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + if (!licenseService.isGoldPlus()) { + return response.customError({ + statusCode: 403, + body: { message: 'Requires Gold license' }, + }); + } + + const soClient = context.core.savedObjects.client; + try { + // Reassign by array of IDs + const result = Array.isArray(request.body.agents) + ? await AgentService.reassignAgents( + soClient, + { agentIds: request.body.agents }, + request.body.policy_id + ) + : await AgentService.reassignAgents( + soClient, + { kuery: request.body.agents }, + request.body.policy_id + ); + const body: PostBulkAgentReassignResponse = result.saved_objects.reduce((acc, so) => { + return { + ...acc, + [so.id]: { + success: !so.error, + error: so.error || undefined, + }, + }; + }, {}); + return response.ok({ body }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); + } +}; + export const getAgentStatusForAgentPolicyHandler: RequestHandler< undefined, TypeOf diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts index a2e5c742ad6b5..73ed276ba02e7 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts @@ -23,10 +23,13 @@ import { PostAgentAcksRequestParamsJSONSchema, PostAgentAcksRequestBodyJSONSchema, PostAgentUnenrollRequestSchema, + PostBulkAgentUnenrollRequestSchema, GetAgentStatusRequestSchema, PostNewAgentActionRequestSchema, PutAgentReassignRequestSchema, + PostBulkAgentReassignRequestSchema, PostAgentEnrollRequestBodyJSONSchema, + PostAgentUpgradeRequestSchema, } from '../../types'; import { getAgentsHandler, @@ -38,13 +41,15 @@ import { postAgentEnrollHandler, getAgentStatusForAgentPolicyHandler, putAgentsReassignHandler, + postBulkAgentsReassignHandler, } from './handlers'; import { postAgentAcksHandlerBuilder } from './acks_handlers'; import * as AgentService from '../../services/agents'; import { postNewAgentActionHandlerBuilder } from './actions_handlers'; import { appContextService } from '../../services'; -import { postAgentsUnenrollHandler } from './unenroll_handler'; +import { postAgentUnenrollHandler, postBulkAgentsUnenrollHandler } from './unenroll_handler'; import { IngestManagerConfigType } from '../..'; +import { postAgentUpgradeHandler } from './upgrade_handler'; const ajv = new Ajv({ coerceTypes: true, @@ -181,7 +186,7 @@ export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) validate: PostAgentUnenrollRequestSchema, options: { tags: [`access:${PLUGIN_ID}-all`] }, }, - postAgentsUnenrollHandler + postAgentUnenrollHandler ); router.put( @@ -212,4 +217,32 @@ export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) }, getAgentStatusForAgentPolicyHandler ); + // upgrade agent + router.post( + { + path: AGENT_API_ROUTES.UPGRADE_PATTERN, + validate: PostAgentUpgradeRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + postAgentUpgradeHandler + ); + // Bulk reassign + router.post( + { + path: AGENT_API_ROUTES.BULK_REASSIGN_PATTERN, + validate: PostBulkAgentReassignRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + postBulkAgentsReassignHandler + ); + + // Bulk unenroll + router.post( + { + path: AGENT_API_ROUTES.BULK_UNENROLL_PATTERN, + validate: PostBulkAgentUnenrollRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + postBulkAgentsUnenrollHandler + ); }; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts index fa200e912d625..861d7c45c6f0a 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts @@ -6,12 +6,13 @@ import { RequestHandler } from 'src/core/server'; import { TypeOf } from '@kbn/config-schema'; -import { PostAgentUnenrollResponse } from '../../../common/types'; -import { PostAgentUnenrollRequestSchema } from '../../types'; +import { PostAgentUnenrollResponse, PostBulkAgentUnenrollResponse } from '../../../common/types'; +import { PostAgentUnenrollRequestSchema, PostBulkAgentUnenrollRequestSchema } from '../../types'; +import { licenseService } from '../../services'; import * as AgentService from '../../services/agents'; import { defaultIngestErrorHandler } from '../../errors'; -export const postAgentsUnenrollHandler: RequestHandler< +export const postAgentUnenrollHandler: RequestHandler< TypeOf, undefined, TypeOf @@ -30,3 +31,32 @@ export const postAgentsUnenrollHandler: RequestHandler< return defaultIngestErrorHandler({ error, response }); } }; + +export const postBulkAgentsUnenrollHandler: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + if (!licenseService.isGoldPlus()) { + return response.customError({ + statusCode: 403, + body: { message: 'Requires Gold license' }, + }); + } + const soClient = context.core.savedObjects.client; + const unenrollAgents = + request.body?.force === true ? AgentService.forceUnenrollAgents : AgentService.unenrollAgents; + + try { + if (Array.isArray(request.body.agents)) { + await unenrollAgents(soClient, { agentIds: request.body.agents }); + } else { + await unenrollAgents(soClient, { kuery: request.body.agents }); + } + + const body: PostBulkAgentUnenrollResponse = {}; + return response.ok({ body }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts new file mode 100644 index 0000000000000..e5d7a44c00768 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandler } from 'src/core/server'; +import { TypeOf } from '@kbn/config-schema'; +import { PostAgentUpgradeResponse } from '../../../common/types'; +import { PostAgentUpgradeRequestSchema } from '../../types'; +import * as AgentService from '../../services/agents'; +import { appContextService } from '../../services'; +import { defaultIngestErrorHandler } from '../../errors'; + +export const postAgentUpgradeHandler: RequestHandler< + TypeOf, + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const { version, source_uri: sourceUri } = request.body; + + // temporarily only allow upgrading to the same version as the installed kibana version + const kibanaVersion = appContextService.getKibanaVersion(); + if (kibanaVersion !== version) { + return response.customError({ + statusCode: 400, + body: { + message: `cannot upgrade agent to ${version} because it is different than the installed kibana version ${kibanaVersion}`, + }, + }); + } + + try { + await AgentService.sendUpgradeAgentAction({ + soClient, + agentId: request.params.agentId, + version, + sourceUri, + }); + + const body: PostAgentUpgradeResponse = {}; + return response.ok({ body }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts index e86f7b24e2c78..fd08b76a3916b 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts @@ -68,6 +68,8 @@ const savedObjectTypes: { [key: string]: SavedObjectsType } = { enrolled_at: { type: 'date' }, unenrolled_at: { type: 'date' }, unenrollment_started_at: { type: 'date' }, + upgraded_at: { type: 'date' }, + upgrade_started_at: { type: 'date' }, access_api_key_id: { type: 'keyword' }, version: { type: 'keyword' }, user_provided_metadata: { type: 'flattened' }, diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy.ts b/x-pack/plugins/ingest_manager/server/services/agent_policy.ts index 938cfb4351630..64b11512fae10 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_policy.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_policy.ts @@ -26,6 +26,7 @@ import { packagePolicyService } from './package_policy'; import { outputService } from './output'; import { agentPolicyUpdateEventHandler } from './agent_policy_update'; import { getSettings } from './settings'; +import { normalizeKuery } from './saved_object'; const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE; @@ -166,13 +167,7 @@ class AgentPolicyService { sortOrder, page, perPage, - // To ensure users don't need to know about SO data structure... - filter: kuery - ? kuery.replace( - new RegExp(`${SAVED_OBJECT_TYPE}\.`, 'g'), - `${SAVED_OBJECT_TYPE}.attributes.` - ) - : undefined, + filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, }); const agentPolicies = await Promise.all( diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts index 1392710eb0eff..e22ee4256b0e2 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts @@ -28,6 +28,7 @@ import { } from '../../constants'; import { getAgentActionByIds } from './actions'; import { forceUnenrollAgent } from './unenroll'; +import { ackAgentUpgraded } from './upgrade'; const ALLOWED_ACKNOWLEDGEMENT_TYPE: string[] = ['ACTION_RESULT']; @@ -80,6 +81,11 @@ export async function acknowledgeAgentActions( await forceUnenrollAgent(soClient, agent.id); } + const upgradeAction = actions.find((action) => action.type === 'UPGRADE'); + if (upgradeAction) { + await ackAgentUpgraded(soClient, upgradeAction); + } + const configChangeAction = getLatestConfigChangePolicyActionIfUpdated(agent, actions); await soClient.bulkUpdate([ diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts index 254c2c8b21e32..f018eea61e4f3 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts @@ -29,12 +29,20 @@ export async function createAgentAction( return createAction(soClient, newAgentAction); } +export async function bulkCreateAgentActions( + soClient: SavedObjectsClientContract, + newAgentActions: Array> +): Promise { + return bulkCreateActions(soClient, newAgentActions); +} + export function createAgentPolicyAction( soClient: SavedObjectsClientContract, newAgentAction: Omit ): Promise { return createAction(soClient, newAgentAction); } + async function createAction( soClient: SavedObjectsClientContract, newAgentAction: Omit @@ -47,19 +55,25 @@ async function createAction( soClient: SavedObjectsClientContract, newAgentAction: Omit | Omit ): Promise { - const so = await soClient.create(AGENT_ACTION_SAVED_OBJECT_TYPE, { - ...newAgentAction, - data: newAgentAction.data ? JSON.stringify(newAgentAction.data) : undefined, - ack_data: newAgentAction.ack_data ? JSON.stringify(newAgentAction.ack_data) : undefined, - }); + const actionSO = await soClient.create( + AGENT_ACTION_SAVED_OBJECT_TYPE, + { + ...newAgentAction, + data: newAgentAction.data ? JSON.stringify(newAgentAction.data) : undefined, + ack_data: newAgentAction.ack_data ? JSON.stringify(newAgentAction.ack_data) : undefined, + } + ); - if (isAgentActionSavedObject(so)) { - const agentAction = savedObjectToAgentAction(so); + if (isAgentActionSavedObject(actionSO)) { + const agentAction = savedObjectToAgentAction(actionSO); + // Action `data` is encrypted, so is not returned from the saved object + // so we add back the original value from the request to form the expected + // response shape for POST create agent action endpoint agentAction.data = newAgentAction.data; return agentAction; - } else if (isPolicyActionSavedObject(so)) { - const agentAction = savedObjectToAgentAction(so); + } else if (isPolicyActionSavedObject(actionSO)) { + const agentAction = savedObjectToAgentAction(actionSO); agentAction.data = newAgentAction.data; return agentAction; @@ -67,6 +81,44 @@ async function createAction( throw new Error('Invalid action'); } +async function bulkCreateActions( + soClient: SavedObjectsClientContract, + newAgentActions: Array> +): Promise; +async function bulkCreateActions( + soClient: SavedObjectsClientContract, + newAgentActions: Array> +): Promise; +async function bulkCreateActions( + soClient: SavedObjectsClientContract, + newAgentActions: Array | Omit> +): Promise> { + const { saved_objects: actionSOs } = await soClient.bulkCreate( + newAgentActions.map((newAgentAction) => ({ + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + attributes: { + ...newAgentAction, + data: newAgentAction.data ? JSON.stringify(newAgentAction.data) : undefined, + ack_data: newAgentAction.ack_data ? JSON.stringify(newAgentAction.ack_data) : undefined, + }, + })) + ); + + return actionSOs.map((actionSO) => { + if (isAgentActionSavedObject(actionSO)) { + const agentAction = savedObjectToAgentAction(actionSO); + // Compared to single create (createAction()), we don't add back the + // original value of `agentAction.data` as this method isn't exposed + // via an HTTP endpoint + return agentAction; + } else if (isPolicyActionSavedObject(actionSO)) { + const agentAction = savedObjectToAgentAction(actionSO); + return agentAction; + } + throw new Error('Invalid action'); + }); +} + export async function getAgentActionsForCheckin( soClient: SavedObjectsClientContract, agentId: string @@ -173,7 +225,11 @@ export async function getAgentPolicyActionByIds( ); } -export async function getNewActionsSince(soClient: SavedObjectsClientContract, timestamp: string) { +export async function getNewActionsSince( + soClient: SavedObjectsClientContract, + timestamp: string, + decryptData: boolean = true +) { const filter = nodeTypes.function.buildNode('and', [ nodeTypes.function.buildNode( 'not', @@ -191,14 +247,33 @@ export async function getNewActionsSince(soClient: SavedObjectsClientContract, t } ), ]); - const res = await soClient.find({ - type: AGENT_ACTION_SAVED_OBJECT_TYPE, - filter, - }); - return res.saved_objects + const actions = ( + await soClient.find({ + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + filter, + }) + ).saved_objects .filter(isAgentActionSavedObject) .map((so) => savedObjectToAgentAction(so)); + + if (!decryptData) { + return actions; + } + + return await Promise.all( + actions.map(async (action) => { + // Get decrypted actions + return savedObjectToAgentAction( + await appContextService + .getEncryptedSavedObjects() + .getDecryptedAsInternalUser( + AGENT_ACTION_SAVED_OBJECT_TYPE, + action.id + ) + ); + }) + ); } export async function getLatestConfigChangeAction( diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.test.ts new file mode 100644 index 0000000000000..5e84e3a50bb44 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TestScheduler } from 'rxjs/testing'; +import { createRateLimiter } from './rxjs_utils'; + +describe('createRateLimiter', () => { + it('should rate limit correctly with 1 request per 10ms', async () => { + const scheduler = new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + + scheduler.run(({ expectObservable, cold }) => { + const source = cold('a-b-c-d-e-f|'); + const rateLimiter = createRateLimiter(10, 1, 2, scheduler); + const obs = source.pipe(rateLimiter()); + const results = 'a 9ms b 9ms c 9ms d 9ms e 9ms (f|)'; + expectObservable(obs).toBe(results); + }); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts index dddade6841460..3bbfbbd4ec1bf 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts @@ -5,6 +5,7 @@ */ import * as Rx from 'rxjs'; +import { concatMap, delay } from 'rxjs/operators'; export class AbortError extends Error {} @@ -45,63 +46,35 @@ export const toPromiseAbortable = ( export function createRateLimiter( ratelimitIntervalMs: number, - ratelimitRequestPerInterval: number + ratelimitRequestPerInterval: number, + maxDelay: number, + scheduler = Rx.asyncScheduler ) { - function createCurrentInterval() { - return { - startedAt: Rx.asyncScheduler.now(), - numRequests: 0, - }; - } + let intervalEnd = 0; + let countInCurrentInterval = 0; - let currentInterval: { startedAt: number; numRequests: number } = createCurrentInterval(); - let observers: Array<[Rx.Subscriber, any]> = []; - let timerSubscription: Rx.Subscription | undefined; + function createRateLimitOperator(): Rx.OperatorFunction { + return Rx.pipe( + concatMap(function rateLimit(value: T) { + const now = scheduler.now(); + if (intervalEnd <= now) { + countInCurrentInterval = 1; + intervalEnd = now + ratelimitIntervalMs; + return Rx.of(value); + } else if (intervalEnd >= now + maxDelay) { + // re-rate limit in the future to avoid to schedule too far in the future as some observer can unsubscribe + return Rx.of(value).pipe(delay(maxDelay, scheduler), createRateLimitOperator()); + } else { + if (++countInCurrentInterval > ratelimitRequestPerInterval) { + countInCurrentInterval = 1; + intervalEnd += ratelimitIntervalMs; + } - function createTimeout() { - if (timerSubscription) { - return; - } - timerSubscription = Rx.asyncScheduler.schedule(() => { - timerSubscription = undefined; - currentInterval = createCurrentInterval(); - for (const [waitingObserver, value] of observers) { - if (currentInterval.numRequests >= ratelimitRequestPerInterval) { - createTimeout(); - continue; + const wait = intervalEnd - ratelimitIntervalMs - now; + return wait > 0 ? Rx.of(value).pipe(delay(wait, scheduler)) : Rx.of(value); } - currentInterval.numRequests++; - waitingObserver.next(value); - } - }, ratelimitIntervalMs); + }) + ); } - - return function limit(): Rx.MonoTypeOperatorFunction { - return (observable) => - new Rx.Observable((observer) => { - const subscription = observable.subscribe({ - next(value) { - if (currentInterval.numRequests < ratelimitRequestPerInterval) { - currentInterval.numRequests++; - observer.next(value); - return; - } - - observers = [...observers, [observer, value]]; - createTimeout(); - }, - error(err) { - observer.error(err); - }, - complete() { - observer.complete(); - }, - }); - - return () => { - observers = observers.filter((o) => o[0] !== observer); - subscription.unsubscribe(); - }; - }); - }; + return createRateLimitOperator; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts index 4122677a615ca..fbbed87b031e2 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts @@ -10,7 +10,6 @@ import { shareReplay, distinctUntilKeyChanged, switchMap, - mergeMap, merge, filter, timeout, @@ -33,6 +32,8 @@ import { import { appContextService } from '../../app_context'; import { toPromiseAbortable, AbortError, createRateLimiter } from './rxjs_utils'; +const RATE_LIMIT_MAX_DELAY_MS = 5 * 60 * 1000; // 5 minutes + function getInternalUserSOClient() { const fakeRequest = ({ headers: {}, @@ -135,11 +136,19 @@ export function agentCheckinStateNewActionsFactory() { const agentPolicies$ = new Map>(); const newActions$ = createNewActionsSharedObservable(); // Rx operators - const rateLimiter = createRateLimiter( + const pollingTimeoutMs = appContextService.getConfig()?.fleet.pollingRequestTimeout ?? 0; + const rateLimiterIntervalMs = appContextService.getConfig()?.fleet.agentPolicyRolloutRateLimitIntervalMs ?? - AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS, + AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS; + const rateLimiterRequestPerInterval = appContextService.getConfig()?.fleet.agentPolicyRolloutRateLimitRequestPerInterval ?? - AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL + AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL; + const rateLimiterMaxDelay = Math.min(RATE_LIMIT_MAX_DELAY_MS, pollingTimeoutMs); + + const rateLimiter = createRateLimiter( + rateLimiterIntervalMs, + rateLimiterRequestPerInterval, + rateLimiterMaxDelay ); async function subscribeToNewActions( @@ -162,7 +171,7 @@ export function agentCheckinStateNewActionsFactory() { const stream$ = agentPolicy$.pipe( timeout( // Set a timeout 3s before the real timeout to have a chance to respond an empty response before socket timeout - Math.max((appContextService.getConfig()?.fleet.pollingRequestTimeout ?? 0) - 3000, 3000) + Math.max(pollingTimeoutMs - 3000, 3000) ), filter( (action) => @@ -173,9 +182,9 @@ export function agentCheckinStateNewActionsFactory() { (!agent.policy_revision || action.policy_revision > agent.policy_revision) ), rateLimiter(), - mergeMap((policyAction) => createAgentActionFromPolicyAction(soClient, agent, policyAction)), + switchMap((policyAction) => createAgentActionFromPolicyAction(soClient, agent, policyAction)), merge(newActions$), - mergeMap(async (data) => { + switchMap(async (data) => { if (!data) { return; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/crud.ts b/x-pack/plugins/ingest_manager/server/services/agents/crud.ts index a57735e25ff7b..c941b0512e597 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/crud.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/crud.ts @@ -3,25 +3,37 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import Boom from 'boom'; import { SavedObjectsClientContract } from 'src/core/server'; -import { - AGENT_SAVED_OBJECT_TYPE, - AGENT_EVENT_SAVED_OBJECT_TYPE, - AGENT_TYPE_EPHEMERAL, - AGENT_POLLING_THRESHOLD_MS, -} from '../../constants'; +import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants'; import { AgentSOAttributes, Agent, AgentEventSOAttributes, ListWithKuery } from '../../types'; +import { escapeSearchQueryPhrase, normalizeKuery, findAllSOs } from '../saved_object'; import { savedObjectToAgent } from './saved_objects'; -import { escapeSearchQueryPhrase } from '../saved_object'; + +const ACTIVE_AGENT_CONDITION = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true`; +const INACTIVE_AGENT_CONDITION = `NOT (${ACTIVE_AGENT_CONDITION})`; + +function _joinFilters(filters: string[], operator = 'AND') { + return filters.reduce((acc: string | undefined, filter) => { + if (acc) { + return `${acc} ${operator} (${filter})`; + } + + return `(${filter})`; + }, undefined); +} export async function listAgents( soClient: SavedObjectsClientContract, options: ListWithKuery & { showInactive: boolean; } -) { +): Promise<{ + agents: Agent[]; + total: number; + page: number; + perPage: number; +}> { const { page = 1, perPage = 20, @@ -30,47 +42,86 @@ export async function listAgents( kuery, showInactive = false, } = options; - const filters = []; if (kuery && kuery !== '') { - // To ensure users dont need to know about SO data structure... - filters.push( - kuery.replace( - new RegExp(`${AGENT_SAVED_OBJECT_TYPE}\.`, 'g'), - `${AGENT_SAVED_OBJECT_TYPE}.attributes.` - ) - ); + filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); } if (showInactive === false) { - const agentActiveCondition = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true AND not ${AGENT_SAVED_OBJECT_TYPE}.attributes.type:${AGENT_TYPE_EPHEMERAL}`; - const recentlySeenEphemeralAgent = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true AND ${AGENT_SAVED_OBJECT_TYPE}.attributes.type:${AGENT_TYPE_EPHEMERAL} AND ${AGENT_SAVED_OBJECT_TYPE}.attributes.last_checkin > ${ - Date.now() - 3 * AGENT_POLLING_THRESHOLD_MS - }`; - filters.push(`(${agentActiveCondition}) OR (${recentlySeenEphemeralAgent})`); + filters.push(ACTIVE_AGENT_CONDITION); } - // eslint-disable-next-line @typescript-eslint/naming-convention - const { saved_objects, total } = await soClient.find({ + const { saved_objects: agentSOs, total } = await soClient.find({ type: AGENT_SAVED_OBJECT_TYPE, + filter: _joinFilters(filters), sortField, sortOrder, page, perPage, - filter: _joinFilters(filters), }); - const agents: Agent[] = saved_objects.map(savedObjectToAgent); - return { - agents, + agents: agentSOs.map(savedObjectToAgent), total, page, perPage, }; } +export async function listAllAgents( + soClient: SavedObjectsClientContract, + options: Omit & { + showInactive: boolean; + } +): Promise<{ + agents: Agent[]; + total: number; +}> { + const { sortField = 'enrolled_at', sortOrder = 'desc', kuery, showInactive = false } = options; + const filters = []; + + if (kuery && kuery !== '') { + filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); + } + + if (showInactive === false) { + filters.push(ACTIVE_AGENT_CONDITION); + } + + const { saved_objects: agentSOs, total } = await findAllSOs(soClient, { + type: AGENT_SAVED_OBJECT_TYPE, + kuery: _joinFilters(filters), + sortField, + sortOrder, + }); + + return { + agents: agentSOs.map(savedObjectToAgent), + total, + }; +} + +export async function countInactiveAgents( + soClient: SavedObjectsClientContract, + options: Pick +): Promise { + const { kuery } = options; + const filters = [INACTIVE_AGENT_CONDITION]; + + if (kuery && kuery !== '') { + filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); + } + + const { total } = await soClient.find({ + type: AGENT_SAVED_OBJECT_TYPE, + filter: _joinFilters(filters), + perPage: 0, + }); + + return total; +} + export async function getAgent(soClient: SavedObjectsClientContract, agentId: string) { const agent = savedObjectToAgent( await soClient.get(AGENT_SAVED_OBJECT_TYPE, agentId) @@ -78,6 +129,17 @@ export async function getAgent(soClient: SavedObjectsClientContract, agentId: st return agent; } +export async function getAgents(soClient: SavedObjectsClientContract, agentIds: string[]) { + const agentSOs = await soClient.bulkGet( + agentIds.map((agentId) => ({ + id: agentId, + type: AGENT_SAVED_OBJECT_TYPE, + })) + ); + const agents = agentSOs.saved_objects.map(savedObjectToAgent); + return agents; +} + export async function getAgentByAccessAPIKeyId( soClient: SavedObjectsClientContract, accessAPIKeyId: string @@ -142,13 +204,3 @@ export async function deleteAgent(soClient: SavedObjectsClientContract, agentId: active: false, }); } - -function _joinFilters(filters: string[], operator = 'AND') { - return filters.reduce((acc: string | undefined, filter) => { - if (acc) { - return `${acc} ${operator} (${filter})`; - } - - return `(${filter})`; - }, undefined); -} diff --git a/x-pack/plugins/ingest_manager/server/services/agents/events.ts b/x-pack/plugins/ingest_manager/server/services/agents/events.ts index dfa599e4ffdfd..627fe4f231d3d 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/events.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/events.ts @@ -7,6 +7,7 @@ import { SavedObjectsClientContract } from 'src/core/server'; import { AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants'; import { AgentEventSOAttributes, AgentEvent } from '../../types'; +import { normalizeKuery } from '../saved_object'; export async function getAgentEvents( soClient: SavedObjectsClientContract, @@ -23,12 +24,7 @@ export async function getAgentEvents( const { total, saved_objects } = await soClient.find({ type: AGENT_EVENT_SAVED_OBJECT_TYPE, filter: - kuery && kuery !== '' - ? kuery.replace( - new RegExp(`${AGENT_EVENT_SAVED_OBJECT_TYPE}\.`, 'g'), - `${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.` - ) - : undefined, + kuery && kuery !== '' ? normalizeKuery(AGENT_EVENT_SAVED_OBJECT_TYPE, kuery) : undefined, perPage, page, sortField: 'timestamp', diff --git a/x-pack/plugins/ingest_manager/server/services/agents/index.ts b/x-pack/plugins/ingest_manager/server/services/agents/index.ts index 400c099af4e93..c878b666bde88 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/index.ts @@ -9,6 +9,7 @@ export * from './events'; export * from './checkin'; export * from './enroll'; export * from './unenroll'; +export * from './upgrade'; export * from './status'; export * from './crud'; export * from './update'; diff --git a/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts b/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts index 3075e146093e3..345c07511f032 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts @@ -9,6 +9,7 @@ import Boom from 'boom'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { AgentSOAttributes } from '../../types'; import { agentPolicyService } from '../agent_policy'; +import { getAgents, listAllAgents } from './crud'; export async function reassignAgent( soClient: SavedObjectsClientContract, @@ -25,3 +26,44 @@ export async function reassignAgent( policy_revision: null, }); } + +export async function reassignAgents( + soClient: SavedObjectsClientContract, + options: + | { + agentIds: string[]; + } + | { + kuery: string; + }, + newAgentPolicyId: string +) { + const agentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId); + if (!agentPolicy) { + throw Boom.notFound(`Agent policy not found: ${newAgentPolicyId}`); + } + + // Filter to agents that do not already use the new agent policy ID + const agents = + 'agentIds' in options + ? await getAgents(soClient, options.agentIds) + : ( + await listAllAgents(soClient, { + kuery: options.kuery, + showInactive: false, + }) + ).agents; + const agentsToUpdate = agents.filter((agent) => agent.policy_id !== newAgentPolicyId); + + // Update the necessary agents + return await soClient.bulkUpdate( + agentsToUpdate.map((agent) => ({ + type: AGENT_SAVED_OBJECT_TYPE, + id: agent.id, + attributes: { + policy_id: newAgentPolicyId, + policy_revision: null, + }, + })) + ); +} diff --git a/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts b/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts index e0ac2620cafd3..60533e1285141 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts @@ -3,13 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { chunk } from 'lodash'; import { SavedObjectsClientContract } from 'src/core/server'; import { AgentSOAttributes } from '../../types'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { getAgent } from './crud'; import * as APIKeyService from '../api_keys'; -import { createAgentAction } from './actions'; +import { createAgentAction, bulkCreateAgentActions } from './actions'; +import { getAgents, listAllAgents } from './crud'; export async function unenrollAgent(soClient: SavedObjectsClientContract, agentId: string) { const now = new Date().toISOString(); @@ -23,6 +24,53 @@ export async function unenrollAgent(soClient: SavedObjectsClientContract, agentI }); } +export async function unenrollAgents( + soClient: SavedObjectsClientContract, + options: + | { + agentIds: string[]; + } + | { + kuery: string; + } +) { + // Filter to agents that do not already unenrolled, or unenrolling + const agents = + 'agentIds' in options + ? await getAgents(soClient, options.agentIds) + : ( + await listAllAgents(soClient, { + kuery: options.kuery, + showInactive: false, + }) + ).agents; + const agentsToUpdate = agents.filter( + (agent) => !agent.unenrollment_started_at && !agent.unenrolled_at + ); + const now = new Date().toISOString(); + + // Create unenroll action for each agent + await bulkCreateAgentActions( + soClient, + agentsToUpdate.map((agent) => ({ + agent_id: agent.id, + created_at: now, + type: 'UNENROLL', + })) + ); + + // Update the necessary agents + return await soClient.bulkUpdate( + agentsToUpdate.map((agent) => ({ + type: AGENT_SAVED_OBJECT_TYPE, + id: agent.id, + attributes: { + unenrollment_started_at: now, + }, + })) + ); +} + export async function forceUnenrollAgent(soClient: SavedObjectsClientContract, agentId: string) { const agent = await getAgent(soClient, agentId); @@ -40,3 +88,63 @@ export async function forceUnenrollAgent(soClient: SavedObjectsClientContract, a unenrolled_at: new Date().toISOString(), }); } + +export async function forceUnenrollAgents( + soClient: SavedObjectsClientContract, + options: + | { + agentIds: string[]; + } + | { + kuery: string; + } +) { + // Filter to agents that are not already unenrolled + const agents = + 'agentIds' in options + ? await getAgents(soClient, options.agentIds) + : ( + await listAllAgents(soClient, { + kuery: options.kuery, + showInactive: false, + }) + ).agents; + const agentsToUpdate = agents.filter((agent) => !agent.unenrolled_at); + const now = new Date().toISOString(); + const apiKeys: string[] = []; + + // Get all API keys that need to be invalidated + agentsToUpdate.forEach((agent) => { + if (agent.access_api_key_id) { + apiKeys.push(agent.access_api_key_id); + } + if (agent.default_api_key_id) { + apiKeys.push(agent.default_api_key_id); + } + }); + + // Invalidate all API keys + // ES doesn't provide a bulk invalidate API, so this could take a long time depending on + // number of keys to invalidate. We run these in batches to avoid overloading ES. + if (apiKeys.length) { + const BATCH_SIZE = 500; + const batches = chunk(apiKeys, BATCH_SIZE); + for (const apiKeysBatch of batches) { + await Promise.all( + apiKeysBatch.map((apiKey) => APIKeyService.invalidateAPIKey(soClient, apiKey)) + ); + } + } + + // Update the necessary agents + return await soClient.bulkUpdate( + agentsToUpdate.map((agent) => ({ + type: AGENT_SAVED_OBJECT_TYPE, + id: agent.id, + attributes: { + active: false, + unenrolled_at: now, + }, + })) + ); +} diff --git a/x-pack/plugins/ingest_manager/server/services/agents/upgrade.ts b/x-pack/plugins/ingest_manager/server/services/agents/upgrade.ts new file mode 100644 index 0000000000000..cee3bc69f25db --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/agents/upgrade.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'src/core/server'; +import { AgentSOAttributes, AgentAction, AgentActionSOAttributes } from '../../types'; +import { AGENT_ACTION_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { createAgentAction } from './actions'; + +export async function sendUpgradeAgentAction({ + soClient, + agentId, + version, + sourceUri, +}: { + soClient: SavedObjectsClientContract; + agentId: string; + version: string; + sourceUri: string; +}) { + const now = new Date().toISOString(); + const data = { + version, + source_uri: sourceUri, + }; + await createAgentAction(soClient, { + agent_id: agentId, + created_at: now, + data, + ack_data: data, + type: 'UPGRADE', + }); + await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentId, { + upgraded_at: undefined, + upgrade_started_at: now, + }); +} + +export async function ackAgentUpgraded( + soClient: SavedObjectsClientContract, + agentAction: AgentAction +) { + const { + attributes: { ack_data: ackData }, + } = await soClient.get(AGENT_ACTION_SAVED_OBJECT_TYPE, agentAction.id); + if (!ackData) throw new Error('data missing from UPGRADE action'); + const { version } = JSON.parse(ackData); + if (!version) throw new Error('version missing from UPGRADE action'); + await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentAction.agent_id, { + upgraded_at: new Date().toISOString(), + local_metadata: { + elastic: { + agent: { + version, + }, + }, + }, + }); +} diff --git a/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts index f058166fc2a4f..ea5d25dc9884f 100644 --- a/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts @@ -12,6 +12,7 @@ import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../constants'; import { createAPIKey, invalidateAPIKey } from './security'; import { agentPolicyService } from '../agent_policy'; import { appContextService } from '../app_context'; +import { normalizeKuery } from '../saved_object'; export async function listEnrollmentApiKeys( soClient: SavedObjectsClientContract, @@ -33,10 +34,7 @@ export async function listEnrollmentApiKeys( sortOrder: 'desc', filter: kuery && kuery !== '' - ? kuery.replace( - new RegExp(`${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}\.`, 'g'), - `${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}.attributes.` - ) + ? normalizeKuery(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, kuery) : undefined, }); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts index b788d1bcbb4a9..6618220a27085 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts @@ -29,9 +29,8 @@ const getDefaultRegistryUrl = (): string => { }; export const getRegistryUrl = (): string => { - const license = licenseService.getLicenseInformation(); const customUrl = appContextService.getConfig()?.registryUrl; - const isGoldPlus = license?.isAvailable && license?.isActive && license?.hasAtLeast('gold'); + const isGoldPlus = licenseService.isGoldPlus(); if (customUrl && isGoldPlus) { return customUrl; diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/ingest_manager/server/services/index.ts index 5942277e90824..7a62c307973c2 100644 --- a/x-pack/plugins/ingest_manager/server/services/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/index.ts @@ -7,6 +7,7 @@ import { SavedObjectsClientContract, KibanaRequest } from 'kibana/server'; import { AgentStatus, Agent, EsAssetReference } from '../types'; import * as settingsService from './settings'; +import { getAgent, listAgents } from './agents'; export { ESIndexPatternSavedObjectService } from './es_index_pattern'; export { getRegistryUrl } from './epm/registry/registry_url'; @@ -40,7 +41,7 @@ export interface AgentService { /** * Get an Agent by id */ - getAgent(soClient: SavedObjectsClientContract, agentId: string): Promise; + getAgent: typeof getAgent; /** * Authenticate an agent with access toekn */ @@ -55,20 +56,7 @@ export interface AgentService { /** * List agents */ - listAgents( - soClient: SavedObjectsClientContract, - options: { - page: number; - perPage: number; - kuery?: string; - showInactive: boolean; - } - ): Promise<{ - agents: Agent[]; - total: number; - page: number; - perPage: number; - }>; + listAgents: typeof listAgents; } // Saved object services diff --git a/x-pack/plugins/ingest_manager/server/services/license.ts b/x-pack/plugins/ingest_manager/server/services/license.ts index bd96dbc7e3aff..a67ec9880ec09 100644 --- a/x-pack/plugins/ingest_manager/server/services/license.ts +++ b/x-pack/plugins/ingest_manager/server/services/license.ts @@ -3,36 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Observable, Subscription } from 'rxjs'; -import { ILicense } from '../../../licensing/server'; - -class LicenseService { - private observable: Observable | null = null; - private subscription: Subscription | null = null; - private licenseInformation: ILicense | null = null; - - private updateInformation(licenseInformation: ILicense) { - this.licenseInformation = licenseInformation; - } - - public start(license$: Observable) { - this.observable = license$; - this.subscription = this.observable.subscribe(this.updateInformation.bind(this)); - } - - public stop() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - } - - public getLicenseInformation() { - return this.licenseInformation; - } - - public getLicenseInformation$() { - return this.observable; - } -} +import { LicenseService } from '../../common'; export const licenseService = new LicenseService(); diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.ts b/x-pack/plugins/ingest_manager/server/services/package_policy.ts index b7e1806979db8..3a02544250ff0 100644 --- a/x-pack/plugins/ingest_manager/server/services/package_policy.ts +++ b/x-pack/plugins/ingest_manager/server/services/package_policy.ts @@ -30,6 +30,7 @@ import * as Registry from './epm/registry'; import { getPackageInfo, getInstallation, ensureInstalledPackage } from './epm/packages'; import { getAssetsData } from './epm/packages/assets'; import { createStream } from './epm/agent/agent'; +import { normalizeKuery } from './saved_object'; const SAVED_OBJECT_TYPE = PACKAGE_POLICY_SAVED_OBJECT_TYPE; @@ -211,13 +212,7 @@ class PackagePolicyService { sortOrder, page, perPage, - // To ensure users don't need to know about SO data structure... - filter: kuery - ? kuery.replace( - new RegExp(`${SAVED_OBJECT_TYPE}\.`, 'g'), - `${SAVED_OBJECT_TYPE}.attributes.` - ) - : undefined, + filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, }); return { diff --git a/x-pack/plugins/ingest_manager/server/services/saved_object.ts b/x-pack/plugins/ingest_manager/server/services/saved_object.ts index 8fe7ffcdfc896..06772206d5198 100644 --- a/x-pack/plugins/ingest_manager/server/services/saved_object.ts +++ b/x-pack/plugins/ingest_manager/server/services/saved_object.ts @@ -3,6 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { SavedObjectsClientContract, SavedObjectsFindResponse } from 'src/core/server'; +import { ListWithKuery } from '../types'; /** * Escape a value with double quote to use with saved object search @@ -12,3 +14,70 @@ export function escapeSearchQueryPhrase(val: string): string { return `"${val.replace(/["]/g, '"')}"`; } + +// Adds `.attribute` to any kuery strings that are missing it, this comes from +// internal SO structure. Kuery strings that come from UI will typicall have +// `.attribute` hidden to simplify UX, so this normalizes any kuery string for +// filtering SOs +export const normalizeKuery = (savedObjectType: string, kuery: string): string => { + return kuery.replace( + new RegExp(`${savedObjectType}\.(?!attributes\.)`, 'g'), + `${savedObjectType}.attributes.` + ); +}; + +// Like saved object client `.find()`, but ignores `page` and `perPage` parameters and +// returns *all* matching saved objects by collocating results from all `.find` pages. +// This function actually doesn't offer any additional benefits over `.find()` for now +// due to SO client limitations (see comments below), so is a placeholder for when SO +// client is improved. +export const findAllSOs = async ( + soClient: SavedObjectsClientContract, + options: Omit & { + type: string; + } +): Promise, 'saved_objects' | 'total'>> => { + const { type, sortField, sortOrder, kuery } = options; + let savedObjectResults: SavedObjectsFindResponse['saved_objects'] = []; + + // TODO: This is the default `index.max_result_window` ES setting, which dictates + // the maximum amount of results allowed to be returned from a search. It's possible + // for the actual setting to differ from the default. Can we retrieve the real + // setting in the future? + const searchLimit = 10000; + + const query = { + type, + sortField, + sortOrder, + filter: kuery, + page: 1, + perPage: searchLimit, + }; + + const { saved_objects: initialSOs, total } = await soClient.find(query); + + savedObjectResults = initialSOs; + + // The saved object client can't actually page through more than the first 10,000 + // results, due to the same `index.max_result_window` constraint. The commented out + // code below is an example of paging through rest of results when the SO client + // offers that kind of support. + // if (total > searchLimit) { + // const remainingPages = Math.ceil((total - searchLimit) / searchLimit); + // for (let currentPage = 2; currentPage <= remainingPages + 1; currentPage++) { + // const { saved_objects: currentPageSavedObjects } = await soClient.find({ + // ...query, + // page: currentPage, + // }); + // if (currentPageSavedObjects.length) { + // savedObjectResults = savedObjectResults.concat(currentPageSavedObjects); + // } + // } + // } + + return { + saved_objects: savedObjectResults, + total, + }; +}; diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent.ts b/x-pack/plugins/ingest_manager/server/types/models/agent.ts index b249705fe6c2f..15004e60a6fa4 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/agent.ts @@ -62,7 +62,12 @@ export const AgentEventSchema = schema.object({ }); export const NewAgentActionSchema = schema.object({ - type: schema.oneOf([schema.literal('CONFIG_CHANGE'), schema.literal('UNENROLL')]), + type: schema.oneOf([ + schema.literal('CONFIG_CHANGE'), + schema.literal('UNENROLL'), + schema.literal('UPGRADE'), + ]), data: schema.maybe(schema.any()), + ack_data: schema.maybe(schema.any()), sent_at: schema.maybe(schema.string()), }); diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts index 43ee0c89126e9..3866ef095563e 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts @@ -172,6 +172,23 @@ export const PostAgentUnenrollRequestSchema = { ), }; +export const PostAgentUpgradeRequestSchema = { + params: schema.object({ + agentId: schema.string(), + }), + body: schema.object({ + source_uri: schema.string(), + version: schema.string(), + }), +}; + +export const PostBulkAgentUnenrollRequestSchema = { + body: schema.object({ + agents: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]), + force: schema.maybe(schema.boolean()), + }), +}; + export const PutAgentReassignRequestSchema = { params: schema.object({ agentId: schema.string(), @@ -181,6 +198,13 @@ export const PutAgentReassignRequestSchema = { }), }; +export const PostBulkAgentReassignRequestSchema = { + body: schema.object({ + policy_id: schema.string(), + agents: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]), + }), +}; + export const GetOneAgentEventsRequestSchema = { params: schema.object({ agentId: schema.string(), diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts index f369bfe66f642..2559b93bd606d 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts @@ -5,10 +5,10 @@ */ import { registerTestBed, TestBedConfig, TestBed } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { PipelinesClone } from '../../../public/application/sections/pipelines_clone'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; +import { getClonePath, ROUTES } from '../../../public/application/services/navigation'; export type PipelinesCloneTestBed = TestBed & { actions: ReturnType; @@ -29,8 +29,8 @@ export const PIPELINE_TO_CLONE = { const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}create/${PIPELINE_TO_CLONE.name}`], - componentRoutePath: `${BASE_PATH}create/:name`, + initialEntries: [getClonePath({ clonedPipelineName: PIPELINE_TO_CLONE.name })], + componentRoutePath: ROUTES.clone, }, doMountAsync: true, }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts index ce5ab1faa01be..22f68f12804d6 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts @@ -5,10 +5,10 @@ */ import { registerTestBed, TestBedConfig, TestBed } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { PipelinesCreate } from '../../../public/application/sections/pipelines_create'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; +import { getCreatePath, ROUTES } from '../../../public/application/services/navigation'; export type PipelinesCreateTestBed = TestBed & { actions: ReturnType; @@ -16,8 +16,8 @@ export type PipelinesCreateTestBed = TestBed & { const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}/create`], - componentRoutePath: `${BASE_PATH}/create`, + initialEntries: [getCreatePath()], + componentRoutePath: ROUTES.create, }, doMountAsync: true, }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts index 31c9630086178..5e0739f78eecd 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts @@ -5,10 +5,10 @@ */ import { registerTestBed, TestBedConfig, TestBed } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { PipelinesEdit } from '../../../public/application/sections/pipelines_edit'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; +import { getEditPath, ROUTES } from '../../../public/application/services/navigation'; export type PipelinesEditTestBed = TestBed & { actions: ReturnType; @@ -29,8 +29,8 @@ export const PIPELINE_TO_EDIT = { const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}edit/${PIPELINE_TO_EDIT.name}`], - componentRoutePath: `${BASE_PATH}edit/:name`, + initialEntries: [getEditPath({ pipelineName: PIPELINE_TO_EDIT.name })], + componentRoutePath: ROUTES.edit, }, doMountAsync: true, }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts index 03ffe361bb5a6..43ca849e61aee 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts @@ -6,7 +6,6 @@ import { act } from 'react-dom/test-utils'; -import { BASE_PATH } from '../../../common/constants'; import { registerTestBed, TestBed, @@ -16,11 +15,12 @@ import { } from '../../../../../test_utils'; import { PipelinesList } from '../../../public/application/sections/pipelines_list'; import { WithAppDependencies } from './setup_environment'; +import { getListPath, ROUTES } from '../../../public/application/services/navigation'; const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [BASE_PATH], - componentRoutePath: BASE_PATH, + initialEntries: [getListPath()], + componentRoutePath: ROUTES.list, }, doMountAsync: true, }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx index 6074c64d2bdb0..18ca71f2bb73a 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx @@ -185,30 +185,5 @@ describe('', () => { expect(find('savePipelineError').find('li').length).toBe(8); }); }); - - describe('test pipeline', () => { - beforeEach(async () => { - await act(async () => { - testBed = await setup(); - - const { waitFor } = testBed; - - await waitFor('pipelineForm'); - }); - }); - - test('should open the test pipeline flyout', async () => { - const { actions, exists, find, waitFor } = testBed; - - await act(async () => { - actions.clickAddDocumentsButton(); - await waitFor('testPipelineFlyout'); - }); - - // Verify test pipeline flyout opens - expect(exists('testPipelineFlyout')).toBe(true); - expect(find('testPipelineFlyout.title').text()).toBe('Test pipeline'); - }); - }); }); }); diff --git a/x-pack/plugins/ingest_pipelines/common/constants.ts b/x-pack/plugins/ingest_pipelines/common/constants.ts index 4c6c6fefaad83..0d6f977bfbfed 100644 --- a/x-pack/plugins/ingest_pipelines/common/constants.ts +++ b/x-pack/plugins/ingest_pipelines/common/constants.ts @@ -9,9 +9,9 @@ const basicLicense: LicenseType = 'basic'; export const PLUGIN_ID = 'ingest_pipelines'; -export const PLUGIN_MIN_LICENSE_TYPE = basicLicense; +export const MANAGEMENT_APP_ID = 'management'; -export const BASE_PATH = '/'; +export const PLUGIN_MIN_LICENSE_TYPE = basicLicense; export const API_BASE_PATH = '/api/ingest_pipelines'; diff --git a/x-pack/plugins/ingest_pipelines/kibana.json b/x-pack/plugins/ingest_pipelines/kibana.json index 38d28fbba20b4..2fe87c5e7a162 100644 --- a/x-pack/plugins/ingest_pipelines/kibana.json +++ b/x-pack/plugins/ingest_pipelines/kibana.json @@ -3,7 +3,7 @@ "version": "8.0.0", "server": true, "ui": true, - "requiredPlugins": ["licensing", "management", "features"], + "requiredPlugins": ["licensing", "management", "features", "share"], "optionalPlugins": ["security", "usageCollection"], "configPath": ["xpack", "ingest_pipelines"], "requiredBundles": ["esUiShared", "kibanaReact"] diff --git a/x-pack/plugins/ingest_pipelines/public/application/app.tsx b/x-pack/plugins/ingest_pipelines/public/application/app.tsx index 55b59caab8d60..e78c4d3983183 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/app.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/app.tsx @@ -21,13 +21,14 @@ import { } from '../shared_imports'; import { PipelinesList, PipelinesCreate, PipelinesEdit, PipelinesClone } from './sections'; +import { ROUTES } from './services/navigation'; export const AppWithoutRouter = () => ( - - - - + + + + {/* Catch all */} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx index 5279bd718c16e..ffd82b0bbaf35 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -11,8 +11,6 @@ import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from import { useForm, Form, FormConfig } from '../../../shared_imports'; import { Pipeline, Processor } from '../../../../common/types'; -import './pipeline_form.scss'; - import { OnUpdateHandlerArg, OnUpdateHandler } from '../pipeline_processors_editor'; import { PipelineRequestFlyout } from './pipeline_request_flyout'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx index 6033f34af6825..a7ffe7ba02caa 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx @@ -6,7 +6,7 @@ import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiSpacer, EuiSwitch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiSpacer, EuiSwitch } from '@elastic/eui'; import { Processor } from '../../../../common/types'; @@ -14,15 +14,11 @@ import { getUseField, getFormRow, Field } from '../../../shared_imports'; import { ProcessorsEditorContextProvider, - GlobalOnFailureProcessorsEditor, - ProcessorsEditor, OnUpdateHandler, OnDoneLoadJsonHandler, + PipelineProcessorsEditor, } from '../pipeline_processors_editor'; -import { ProcessorsHeader } from './processors_header'; -import { OnFailureProcessorsTitle } from './on_failure_processors_title'; - interface Props { processors: Processor[]; onFailure?: Processor[]; @@ -118,28 +114,12 @@ export const PipelineFormFields: React.FunctionComponent = ({ {/* Pipeline Processors Editor */} - -
- - - - - - - - - - - - - - -
+
); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx index e46e5156e30f3..10fb73df1ce1c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx @@ -11,12 +11,7 @@ import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mock import { LocationDescriptorObject } from 'history'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; import { registerTestBed, TestBed } from '../../../../../../../test_utils'; -import { - ProcessorsEditorContextProvider, - Props, - ProcessorsEditor, - GlobalOnFailureProcessorsEditor, -} from '../'; +import { ProcessorsEditorContextProvider, Props, PipelineProcessorsEditor } from '../'; import { breadcrumbService, @@ -90,7 +85,7 @@ const testBedSetup = registerTestBed( (props: Props) => ( - + ), @@ -210,4 +205,5 @@ type TestSubject = | 'processorSettingsFormFlyout' | 'processorTypeSelector' | 'pipelineEditorOnFailureTree' + | 'processorsEmptyPrompt' | string; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx index 74ae8b8894b9f..b80d238362118 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx @@ -55,6 +55,23 @@ describe('Pipeline Editor', () => { expect(arg.getData()).toEqual(testProcessors); }); + describe('no processors', () => { + beforeEach(async () => { + testBed = await setup({ + value: { + processors: [], + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + + it('displays an empty prompt if no processors are defined', () => { + const { exists } = testBed; + expect(exists('processorsEmptyPrompt')).toBe(true); + }); + }); + describe('processors', () => { it('adds a new processor', async () => { const { actions } = testBed; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx index 4aabcc1d59d73..03b497320dfbc 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx @@ -6,30 +6,49 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonEmpty } from '@elastic/eui'; +import { EuiButtonEmpty, EuiButton } from '@elastic/eui'; import { usePipelineProcessorsContext } from '../context'; export interface Props { onClick: () => void; + renderButtonAsLink?: boolean; } +const addProcessorButtonLabel = i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.addProcessorButtonLabel', + { + defaultMessage: 'Add a processor', + } +); + export const AddProcessorButton: FunctionComponent = (props) => { - const { onClick } = props; + const { onClick, renderButtonAsLink } = props; const { state: { editor }, } = usePipelineProcessorsContext(); + + if (renderButtonAsLink) { + return ( + + {addProcessorButtonLabel} + + ); + } + return ( - - {i18n.translate('xpack.ingestPipelines.pipelineEditor.addProcessorButtonLabel', { - defaultMessage: 'Add a processor', - })} - + {addProcessorButtonLabel} +
); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts index d476202aa43bb..2e62a81ffa153 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts @@ -19,3 +19,9 @@ export { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json'; export { TestPipelineActions } from './test_pipeline'; export { PipelineProcessorsItemTooltip, Position } from './pipeline_processors_editor_item_tooltip'; + +export { ProcessorsEmptyPrompt } from './processors_empty_prompt'; + +export { ProcessorsHeader } from './processors_header'; + +export { OnFailureProcessorsTitle } from './on_failure_processors_title'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx index 21d15fc86a0ce..38700d6a7a87c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx @@ -15,7 +15,7 @@ interface Props { const i18nTexts = { buttonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.loadFromJson.buttonLabel', { - defaultMessage: 'Import', + defaultMessage: 'Import processors', }), }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx similarity index 96% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx index 0beb5657b54cb..7adc37d1897d1 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx @@ -8,7 +8,7 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useKibana } from '../../../shared_imports'; +import { useKibana } from '../../../../shared_imports'; export const OnFailureProcessorsTitle: FunctionComponent = () => { const { services } = useKibana(); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx index 5231a3d17811b..b663daedd9b9c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx @@ -118,6 +118,7 @@ export const AddProcessorForm: FunctionComponent = ({ { await handleSubmit(); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx index e449ed75b6343..d9feaaffa5aec 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx @@ -234,6 +234,7 @@ export const EditProcessorForm: FunctionComponent = ({ { if (activeTab === 'output') { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/drag_and_drop_text_list.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/drag_and_drop_text_list.scss new file mode 100644 index 0000000000000..2f563d86a6d4a --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/drag_and_drop_text_list.scss @@ -0,0 +1,28 @@ +.pipelineProcessorsEditor__form__dragAndDropList { + &__panel { + background-color: $euiColorLightestShade; + padding: $euiSizeM; + } + + &__grabIcon { + margin-right: $euiSizeS; + } + + &__removeButton { + margin-left: $euiSizeS; + } + + &__errorIcon { + margin-left: -$euiSizeXL; + } + + &__item { + background-color: $euiColorLightestShade; + padding-top: $euiSizeS; + padding-bottom: $euiSizeS; + } + + &__labelContainer { + margin-bottom: $euiSizeXS; + } +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx new file mode 100644 index 0000000000000..63e1fdaa9a8f0 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx @@ -0,0 +1,210 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React, { useState, useCallback, memo } from 'react'; +import uuid from 'uuid'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiDragDropContext, + EuiDraggable, + EuiDroppable, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiFieldText, + EuiIconTip, + EuiFormRow, + EuiText, +} from '@elastic/eui'; + +import { + UseField, + ArrayItem, + ValidationFunc, + getFieldValidityAndErrorMessage, +} from '../../../../../../shared_imports'; + +import './drag_and_drop_text_list.scss'; + +interface Props { + label: string; + helpText: React.ReactNode; + error: string | null; + value: ArrayItem[]; + onMove: (sourceIdx: number, destinationIdx: number) => void; + onAdd: () => void; + onRemove: (id: number) => void; + addLabel: string; + /** + * Validation to be applied to every text item + */ + textValidation?: ValidationFunc; +} + +const i18nTexts = { + removeItemButtonAriaLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.dragAndDropList.removeItemLabel', + { defaultMessage: 'Remove item' } + ), +}; + +function DragAndDropTextListComponent({ + label, + helpText, + error, + value, + onMove, + onAdd, + onRemove, + addLabel, + textValidation, +}: Props): JSX.Element { + const [droppableId] = useState(() => uuid.v4()); + const [firstItemId] = useState(() => uuid.v4()); + + const onDragEnd = useCallback( + ({ source, destination }) => { + if (source && destination) { + onMove(source.index, destination.index); + } + }, + [onMove] + ); + return ( + + <> + {/* Label and help text. Also wire up the htmlFor so the label points to the first text field. */} + + + + + + + + +

{helpText}

+
+
+
+ + {/* The processor panel */} +
+ + + {value.map((item, idx) => { + return ( + + {(provided) => { + return ( + + +
+ +
+
+ + + path={item.path} + config={{ + validations: textValidation + ? [{ validator: textValidation }] + : undefined, + }} + readDefaultValueOnForm={!item.isNew} + > + {(field) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage( + field + ); + return ( + + + + + {typeof errorMessage === 'string' && ( + +
+ +
+
+ )} +
+ ); + }} + +
+ + {value.length > 1 ? ( + onRemove(item.id)} + /> + ) : ( + // Render a no-op placeholder button + + )} + +
+ ); + }} +
+ ); + })} +
+
+ + {addLabel} + +
+ +
+ ); +} + +export const DragAndDropTextList = memo( + DragAndDropTextListComponent +) as typeof DragAndDropTextListComponent; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/index.ts index 6ce9eefd26445..605568f90ce9f 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/index.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ +export { DragAndDropTextList } from './drag_and_drop_text_list'; export { XJsonEditor } from './xjson_editor'; export { TextEditor } from './text_editor'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.scss new file mode 100644 index 0000000000000..f48e19fd0e635 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.scss @@ -0,0 +1,5 @@ +.pipelineProcessorsEditor__form__textEditor { + &__panel { + box-shadow: none; + } +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.tsx index 1d0e36c0d526c..88b4a0aa2be06 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.tsx @@ -13,6 +13,8 @@ import { getFieldValidityAndErrorMessage, } from '../../../../../../shared_imports'; +import './text_editor.scss'; + interface Props { field: FieldHook; editorProps: { [key: string]: any }; @@ -30,7 +32,11 @@ export const TextEditor: FunctionComponent = ({ field, editorProps }) => error={errorMessage} fullWidth > - + diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/xjson_editor.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/xjson_editor.tsx index e00f9c002e5bc..f482e6f08c2c6 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/xjson_editor.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/xjson_editor.tsx @@ -5,7 +5,9 @@ */ import { XJsonLang } from '@kbn/monaco'; import React, { FunctionComponent, useCallback } from 'react'; -import { FieldHook, Monaco } from '../../../../../../shared_imports'; +import { FieldHook, XJson } from '../../../../../../shared_imports'; + +const { useXJsonMode } = XJson; import { TextEditor } from './text_editor'; @@ -21,7 +23,7 @@ const defaultEditorOptions = { export const XJsonEditor: FunctionComponent = ({ field, editorProps }) => { const { value, setValue } = field; - const { xJson, setXJson, convertToJson } = Monaco.useXJsonMode(value); + const { xJson, setXJson, convertToJson } = useXJsonMode(value); const onChange = useCallback( (s) => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx index 332908d0756f2..25c9579e3c48e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx @@ -60,6 +60,7 @@ export const ProcessorFormContainer: FunctionComponent = ({ const { form } = useForm({ defaultValue: { fields: getProcessor().options }, }); + const { subscribe } = form; const handleSubmit = useCallback( async (shouldCloseFlyout: boolean = true) => { @@ -92,14 +93,9 @@ export const ProcessorFormContainer: FunctionComponent = ({ }, [onSubmit, processor]); useEffect(() => { - const subscription = form.subscribe(onFormUpdate); + const subscription = subscribe(onFormUpdate); return subscription.unsubscribe; - - // TODO: Address this issue - // For some reason adding `form` object to the dependencies array here is causing an - // infinite update loop. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [onFormUpdate]); + }, [onFormUpdate, subscribe]); if (processor) { return ( @@ -113,15 +109,15 @@ export const ProcessorFormContainer: FunctionComponent = ({ handleSubmit={handleSubmit} /> ); - } else { - return ( - - ); } + + return ( + + ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/processor_type_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/processor_type_field.tsx index 3264923442886..5b3df63a11294 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/processor_type_field.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/processor_type_field.tsx @@ -14,6 +14,7 @@ import { FieldConfig, UseField, fieldValidators, + useKibana, } from '../../../../../../../shared_imports'; import { getProcessorDescriptor, mapProcessorTypeToDescriptor } from '../../../shared'; @@ -64,6 +65,10 @@ const typeConfig: FieldConfig = { }; export const ProcessorTypeField: FunctionComponent = ({ initialType }) => { + const { + services: { documentation }, + } = useKibana(); + const esDocUrl = documentation.getEsDocsBasePath(); return ( config={typeConfig} defaultValue={initialType} path="type"> {(typeField) => { @@ -107,7 +112,7 @@ export const ProcessorTypeField: FunctionComponent = ({ initialType }) => {}; + (this as any).terminate = () => {}; +}; + +describe('', () => { + const setup = (props?: { defaultValue: Record }) => { + function MyComponent() { + const { form } = useForm({ defaultValue: props?.defaultValue }); + const i18n = i18nServiceMock.createStartContract(); + return ( + + + + + + + + ); + } + return mount(); + }; + + beforeAll(() => { + // disable all react-beautiful-dnd development warnings + (window as any)['__react-beautiful-dnd-disable-dev-warnings'] = true; + }); + + afterAll(() => { + // enable all react-beautiful-dnd development warnings + (window as any)['__react-beautiful-dnd-disable-dev-warnings'] = false; + }); + test('smoke', () => { + setup({ defaultValue: { type: 'grok', fields: { patterns: ['test'] } } }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/grok.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/grok.tsx index c5c6adbe2a7a8..5df30be3407a2 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/grok.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/grok.tsx @@ -10,24 +10,46 @@ import { i18n } from '@kbn/i18n'; import { FIELD_TYPES, UseField, - ComboBoxField, + UseArray, ToggleField, fieldValidators, + ValidationFunc, + ArrayItem, } from '../../../../../../shared_imports'; -import { XJsonEditor } from '../field_components'; +import { XJsonEditor, DragAndDropTextList } from '../field_components'; import { FieldNameField } from './common_fields/field_name_field'; import { IgnoreMissingField } from './common_fields/ignore_missing_field'; import { FieldsConfig, to, from, EDITOR_PX_HEIGHT } from './shared'; -const { emptyField, isJsonField } = fieldValidators; +const { isJsonField, emptyField } = fieldValidators; + +const i18nTexts = { + addPatternLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.grokForm.patternsAddPatternLabel', + { defaultMessage: 'Add pattern' } + ), +}; + +const valueRequiredMessage = i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.grokForm.patternsValueRequiredError', + { defaultMessage: 'A value is required.' } +); + +const patternsValidation: ValidationFunc = ({ value, formData }) => { + if (value.length === 0) { + return { + message: valueRequiredMessage, + }; + } +}; + +const patternValidation = emptyField(valueRequiredMessage); const fieldsConfig: FieldsConfig = { /* Required field configs */ patterns: { - type: FIELD_TYPES.COMBO_BOX, - deserializer: to.arrayOfStrings, label: i18n.translate('xpack.ingestPipelines.pipelineEditor.grokForm.patternsFieldLabel', { defaultMessage: 'Patterns', }), @@ -37,12 +59,7 @@ const fieldsConfig: FieldsConfig = { }), validations: [ { - validator: emptyField( - i18n.translate( - 'xpack.ingestPipelines.pipelineEditor.grokForm.patternsValueRequiredError', - { defaultMessage: 'A value is required.' } - ) - ), + validator: patternsValidation as ValidationFunc, }, ], }, @@ -103,7 +120,23 @@ export const Grok: FunctionComponent = () => { )} /> - + + {({ items, addItem, removeItem, moveItem, error }) => { + return ( + + ); + }} + = ({ onLoadJson }) => { + const { onTreeAction } = usePipelineProcessorsContext(); + const { services } = useKibana(); + + return ( + {i18nTexts.emptyPromptTitle}} + data-test-subj="processorsEmptyPrompt" + body={ +

+ + {i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.processorsDocumentationLink', + { + defaultMessage: 'Learn more.', + } + )} + + ), + }} + /> +

+ } + actions={ + <> + { + onTreeAction({ type: 'addProcessor', payload: { target: ['processors'] } }); + }} + /> + + + + + + } + /> + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_header.tsx similarity index 78% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_header.tsx index 43477affa8d94..24f3207d6bea4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_header.tsx @@ -9,21 +9,32 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiTitle } from '@elastic/ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useKibana } from '../../../shared_imports'; +import { useKibana } from '../../../../shared_imports'; -import { - LoadFromJsonButton, - OnDoneLoadJsonHandler, - TestPipelineActions, -} from '../pipeline_processors_editor'; +import { LoadFromJsonButton, OnDoneLoadJsonHandler, TestPipelineActions } from './'; export interface Props { onLoadJson: OnDoneLoadJsonHandler; + hasProcessors: boolean; } -export const ProcessorsHeader: FunctionComponent = ({ onLoadJson }) => { +export const ProcessorsHeader: FunctionComponent = ({ onLoadJson, hasProcessors }) => { const { services } = useKibana(); + const ProcessorTitle: FunctionComponent = () => ( + +

+ {i18n.translate('xpack.ingestPipelines.pipelineEditor.processorsTreeTitle', { + defaultMessage: 'Processors', + })} +

+
+ ); + + if (!hasProcessors) { + return ; + } + return ( = ({ onLoadJson }) => { - -

- {i18n.translate('xpack.ingestPipelines.pipelineEditor.processorsTreeTitle', { - defaultMessage: 'Processors', - })} -

-
+
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx index e9008e6f5b693..3a8299c017d8d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx @@ -70,6 +70,7 @@ export const TreeNode: FunctionComponent = ({ /> onAction({ type: 'addProcessor', diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx index 8b344a137f3a8..ffc0a1459b791 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx @@ -99,7 +99,7 @@ export const ProcessorsTree: FunctionComponent = memo((props) => { - + {!processors.length && ( = memo((props) => { onClick={() => { onAction({ type: 'addProcessor', payload: { target: baseSelector } }); }} + renderButtonAsLink /> diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx index 0a094009da2c6..4c98940b0138e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx @@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n'; import React, { ReactNode } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCode, EuiLink } from '@elastic/eui'; -import { useKibana } from '../../../../../shared_imports'; import { Append, @@ -55,7 +54,7 @@ interface FieldDescriptor { * A sentence case label that can be displayed to users */ label: string; - description?: string | ReactNode; + description?: string | ((esDocUrl: string) => ReactNode); } type MapProcessorTypeToDescriptor = Record; @@ -176,11 +175,7 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { label: i18n.translate('xpack.ingestPipelines.processors.label.enrich', { defaultMessage: 'Enrich', }), - description: function Description() { - const { - services: { documentation }, - } = useKibana(); - const esDocUrl = documentation.getEsDocsBasePath(); + description: (esDocUrl) => { return ( { return ( = ({ onLoadJson }) => { + const { + state: { processors: allProcessors }, + } = usePipelineProcessorsContext(); + + const { + state: { processors, onFailure }, + } = allProcessors; + + const showEmptyPrompt = processors.length === 0 && onFailure.length === 0; + + let content: React.ReactNode; + + if (showEmptyPrompt) { + content = ; + } else { + content = ( + <> + + + + + + + + + ); + } + + return ( +
+ + + 0} /> + + + {content} + + +
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx index acca1c4e03f40..d4aa11715248e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx @@ -16,7 +16,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { BASE_PATH } from '../../../../common/constants'; +import { getListPath } from '../../services/navigation'; import { Pipeline } from '../../../../common/types'; import { useKibana } from '../../../shared_imports'; import { PipelineForm } from '../../components'; @@ -50,11 +50,11 @@ export const PipelinesCreate: React.FunctionComponent { - history.push(BASE_PATH); + history.push(getListPath()); }; useEffect(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx index e09cf4820771f..35ca1635ab9c3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx @@ -17,11 +17,11 @@ import { } from '@elastic/eui'; import { EuiCallOut } from '@elastic/eui'; -import { BASE_PATH } from '../../../../common/constants'; import { Pipeline } from '../../../../common/types'; import { useKibana, SectionLoading } from '../../../shared_imports'; -import { PipelineForm } from '../../components'; +import { getListPath } from '../../services/navigation'; +import { PipelineForm } from '../../components'; import { attemptToURIDecode } from '../shared'; interface MatchParams { @@ -56,11 +56,11 @@ export const PipelinesEdit: React.FunctionComponent { - history.push(BASE_PATH); + history.push(getListPath()); }; useEffect(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx index eba69ff454911..7f4caa09b6df0 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx @@ -11,6 +11,7 @@ import { useHistory } from 'react-router-dom'; import { ScopedHistory } from 'kibana/public'; import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { useKibana } from '../../../shared_imports'; +import { getCreatePath } from '../../services/navigation'; export const EmptyList: FunctionComponent = () => { const { services } = useKibana(); @@ -44,7 +45,11 @@ export const EmptyList: FunctionComponent = () => {

} actions={ - + {i18n.translate('xpack.ingestPipelines.list.table.emptyPrompt.createButtonLabel', { defaultMessage: 'Create a pipeline', })} diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx index 88148f1bc5746..be31f86e30c27 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx @@ -24,9 +24,9 @@ import { } from '@elastic/eui'; import { Pipeline } from '../../../../common/types'; -import { BASE_PATH } from '../../../../common/constants'; import { useKibana, SectionLoading } from '../../../shared_imports'; import { UIM_PIPELINES_LIST_LOAD } from '../../constants'; +import { getEditPath, getClonePath, getListPath } from '../../services/navigation'; import { EmptyList } from './empty_list'; import { PipelineTable } from './table'; @@ -67,17 +67,17 @@ export const PipelinesList: React.FunctionComponent = ({ } }, [pipelineNameFromLocation, data]); - const goToEditPipeline = (name: string) => { - history.push(`${BASE_PATH}/edit/${encodeURIComponent(name)}`); + const goToEditPipeline = (pipelineName: string) => { + history.push(getEditPath({ pipelineName })); }; - const goToClonePipeline = (name: string) => { - history.push(`${BASE_PATH}/create/${encodeURIComponent(name)}`); + const goToClonePipeline = (clonedPipelineName: string) => { + history.push(getClonePath({ clonedPipelineName })); }; const goHome = () => { setShowFlyout(false); - history.push(BASE_PATH); + history.push(getListPath()); }; if (data && data.length === 0) { diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts b/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts new file mode 100644 index 0000000000000..3ac3de6eac710 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/services/navigation.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; + * you may not use this file except in compliance with the Elastic License. + */ + +const BASE_PATH = '/'; + +const EDIT_PATH = 'edit'; + +const CREATE_PATH = 'create'; + +const _getEditPath = (name: string, encode = true): string => { + return `${BASE_PATH}${EDIT_PATH}/${encode ? encodeURIComponent(name) : name}`; +}; + +const _getCreatePath = (): string => { + return `${BASE_PATH}${CREATE_PATH}`; +}; + +const _getClonePath = (name: string, encode = true): string => { + return `${BASE_PATH}${CREATE_PATH}/${encode ? encodeURIComponent(name) : name}`; +}; +const _getListPath = (name?: string): string => { + return `${BASE_PATH}${name ? `?pipeline=${encodeURIComponent(name)}` : ''}`; +}; + +export const ROUTES = { + list: _getListPath(), + edit: _getEditPath(':name', false), + create: _getCreatePath(), + clone: _getClonePath(':sourceName', false), +}; + +export const getListPath = ({ + inspectedPipelineName, +}: { + inspectedPipelineName?: string; +} = {}): string => _getListPath(inspectedPipelineName); +export const getEditPath = ({ pipelineName }: { pipelineName: string }): string => + _getEditPath(pipelineName, true); +export const getCreatePath = (): string => _getCreatePath(); +export const getClonePath = ({ clonedPipelineName }: { clonedPipelineName: string }): string => + _getClonePath(clonedPipelineName, true); diff --git a/x-pack/plugins/ingest_pipelines/public/index.ts b/x-pack/plugins/ingest_pipelines/public/index.ts index 7247973703804..637d4aad7264a 100644 --- a/x-pack/plugins/ingest_pipelines/public/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/index.ts @@ -9,3 +9,10 @@ import { IngestPipelinesPlugin } from './plugin'; export function plugin() { return new IngestPipelinesPlugin(); } + +export { + INGEST_PIPELINES_APP_ULR_GENERATOR, + IngestPipelinesUrlGenerator, + IngestPipelinesUrlGeneratorState, + INGEST_PIPELINES_PAGES, +} from './url_generator'; diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts index 339068f185d1d..6c2f4a0898327 100644 --- a/x-pack/plugins/ingest_pipelines/public/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts @@ -10,10 +10,11 @@ import { CoreSetup, Plugin } from 'src/core/public'; import { PLUGIN_ID } from '../common/constants'; import { uiMetricService, apiService } from './application/services'; import { Dependencies } from './types'; +import { registerUrlGenerator } from './url_generator'; export class IngestPipelinesPlugin implements Plugin { public setup(coreSetup: CoreSetup, plugins: Dependencies): void { - const { management, usageCollection } = plugins; + const { management, usageCollection, share } = plugins; const { http, getStartServices } = coreSetup; // Initialize services @@ -46,6 +47,8 @@ export class IngestPipelinesPlugin implements Plugin { }; }, }); + + registerUrlGenerator(coreSetup, management, share); } public start() {} diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts index abdbdf2140400..703b7a90f9356 100644 --- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts @@ -21,7 +21,7 @@ export { useRequest, UseRequestConfig, WithPrivileges, - Monaco, + XJson, JsonEditor, OnJsonEditorUpdateHandler, } from '../../../../src/plugins/es_ui_shared/public/'; @@ -36,6 +36,8 @@ export { ValidationFuncArg, FormData, UseField, + UseArray, + ArrayItem, FormHook, useFormContext, FormDataProvider, @@ -70,4 +72,6 @@ export { isEmptyString, } from '../../../../src/plugins/es_ui_shared/static/validators/string'; +export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; + export const useKibana = () => _useKibana(); diff --git a/x-pack/plugins/ingest_pipelines/public/types.ts b/x-pack/plugins/ingest_pipelines/public/types.ts index 91783ea04fa9a..e968c87226d07 100644 --- a/x-pack/plugins/ingest_pipelines/public/types.ts +++ b/x-pack/plugins/ingest_pipelines/public/types.ts @@ -6,8 +6,10 @@ import { ManagementSetup } from 'src/plugins/management/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; +import { SharePluginSetup } from '../../../../src/plugins/share/public'; export interface Dependencies { management: ManagementSetup; usageCollection: UsageCollectionSetup; + share: SharePluginSetup; } diff --git a/x-pack/plugins/ingest_pipelines/public/url_generator.test.ts b/x-pack/plugins/ingest_pipelines/public/url_generator.test.ts new file mode 100644 index 0000000000000..1267d526fb7d4 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/url_generator.test.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IngestPipelinesUrlGenerator, INGEST_PIPELINES_PAGES } from './url_generator'; + +describe('IngestPipelinesUrlGenerator', () => { + const getAppBasePath = (absolute: boolean = false) => { + if (absolute) { + return Promise.resolve('http://localhost/app/test_app'); + } + return Promise.resolve('/app/test_app'); + }; + const urlGenerator = new IngestPipelinesUrlGenerator(getAppBasePath); + + describe('Pipelines List', () => { + it('generates relative url for list without pipelineId', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.LIST, + }); + expect(url).toBe('/app/test_app/'); + }); + + it('generates absolute url for list without pipelineId', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.LIST, + absolute: true, + }); + expect(url).toBe('http://localhost/app/test_app/'); + }); + it('generates relative url for list with a pipelineId', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.LIST, + pipelineId: 'pipeline_name', + }); + expect(url).toBe('/app/test_app/?pipeline=pipeline_name'); + }); + + it('generates absolute url for list with a pipelineId', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.LIST, + pipelineId: 'pipeline_name', + absolute: true, + }); + expect(url).toBe('http://localhost/app/test_app/?pipeline=pipeline_name'); + }); + }); + + describe('Pipeline Edit', () => { + it('generates relative url for pipeline edit', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.EDIT, + pipelineId: 'pipeline_name', + }); + expect(url).toBe('/app/test_app/edit/pipeline_name'); + }); + + it('generates absolute url for pipeline edit', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.EDIT, + pipelineId: 'pipeline_name', + absolute: true, + }); + expect(url).toBe('http://localhost/app/test_app/edit/pipeline_name'); + }); + }); + + describe('Pipeline Clone', () => { + it('generates relative url for pipeline clone', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.CLONE, + pipelineId: 'pipeline_name', + }); + expect(url).toBe('/app/test_app/create/pipeline_name'); + }); + + it('generates absolute url for pipeline clone', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.CLONE, + pipelineId: 'pipeline_name', + absolute: true, + }); + expect(url).toBe('http://localhost/app/test_app/create/pipeline_name'); + }); + }); + + describe('Pipeline Create', () => { + it('generates relative url for pipeline create', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.CREATE, + pipelineId: 'pipeline_name', + }); + expect(url).toBe('/app/test_app/create'); + }); + + it('generates absolute url for pipeline create', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.CREATE, + pipelineId: 'pipeline_name', + absolute: true, + }); + expect(url).toBe('http://localhost/app/test_app/create'); + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/url_generator.ts b/x-pack/plugins/ingest_pipelines/public/url_generator.ts new file mode 100644 index 0000000000000..043d449a0440a --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/url_generator.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup } from 'src/core/public'; +import { MANAGEMENT_APP_ID } from '../../../../src/plugins/management/public'; +import { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public'; +import { + getClonePath, + getCreatePath, + getEditPath, + getListPath, +} from './application/services/navigation'; +import { Dependencies } from './types'; +import { PLUGIN_ID } from '../common/constants'; + +export const INGEST_PIPELINES_APP_ULR_GENERATOR = 'INGEST_PIPELINES_APP_URL_GENERATOR'; + +export enum INGEST_PIPELINES_PAGES { + LIST = 'pipelines_list', + EDIT = 'pipeline_edit', + CREATE = 'pipeline_create', + CLONE = 'pipeline_clone', +} + +interface UrlGeneratorState { + pipelineId: string; + absolute?: boolean; +} +export interface PipelinesListUrlGeneratorState extends Partial { + page: INGEST_PIPELINES_PAGES.LIST; +} + +export interface PipelineEditUrlGeneratorState extends UrlGeneratorState { + page: INGEST_PIPELINES_PAGES.EDIT; +} + +export interface PipelineCloneUrlGeneratorState extends UrlGeneratorState { + page: INGEST_PIPELINES_PAGES.CLONE; +} + +export interface PipelineCreateUrlGeneratorState extends UrlGeneratorState { + page: INGEST_PIPELINES_PAGES.CREATE; +} + +export type IngestPipelinesUrlGeneratorState = + | PipelinesListUrlGeneratorState + | PipelineEditUrlGeneratorState + | PipelineCloneUrlGeneratorState + | PipelineCreateUrlGeneratorState; + +export class IngestPipelinesUrlGenerator + implements UrlGeneratorsDefinition { + constructor(private readonly getAppBasePath: (absolute: boolean) => Promise) {} + + public readonly id = INGEST_PIPELINES_APP_ULR_GENERATOR; + + public readonly createUrl = async (state: IngestPipelinesUrlGeneratorState): Promise => { + switch (state.page) { + case INGEST_PIPELINES_PAGES.EDIT: { + return `${await this.getAppBasePath(!!state.absolute)}${getEditPath({ + pipelineName: state.pipelineId, + })}`; + } + case INGEST_PIPELINES_PAGES.CREATE: { + return `${await this.getAppBasePath(!!state.absolute)}${getCreatePath()}`; + } + case INGEST_PIPELINES_PAGES.LIST: { + return `${await this.getAppBasePath(!!state.absolute)}${getListPath({ + inspectedPipelineName: state.pipelineId, + })}`; + } + case INGEST_PIPELINES_PAGES.CLONE: { + return `${await this.getAppBasePath(!!state.absolute)}${getClonePath({ + clonedPipelineName: state.pipelineId, + })}`; + } + } + }; +} + +export const registerUrlGenerator = ( + coreSetup: CoreSetup, + management: Dependencies['management'], + share: Dependencies['share'] +) => { + const getAppBasePath = async (absolute = false) => { + const [coreStart] = await coreSetup.getStartServices(); + return coreStart.application.getUrlForApp(MANAGEMENT_APP_ID, { + path: management.sections.section.ingest.getApp(PLUGIN_ID)!.basePath, + absolute: !!absolute, + }); + }; + + share.urlGenerators.registerUrlGenerator(new IngestPipelinesUrlGenerator(getAppBasePath)); +}; diff --git a/x-pack/plugins/lens/common/constants.ts b/x-pack/plugins/lens/common/constants.ts index ea2331a577743..d30ab5962667d 100644 --- a/x-pack/plugins/lens/common/constants.ts +++ b/x-pack/plugins/lens/common/constants.ts @@ -8,15 +8,16 @@ export const PLUGIN_ID = 'lens'; export const LENS_EMBEDDABLE_TYPE = 'lens'; export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; export const BASE_API_URL = '/api/lens'; +export const LENS_EDIT_BY_VALUE = 'edit_by_value'; export function getBasePath() { return `#/`; } -export function getEditPath(id: string) { - return `#/edit/${encodeURIComponent(id)}`; +export function getEditPath(id: string | undefined) { + return id ? `#/edit/${encodeURIComponent(id)}` : `#/${LENS_EDIT_BY_VALUE}`; } -export function getFullPath(id: string) { - return `/app/${PLUGIN_ID}${getEditPath(id)}`; +export function getFullPath(id?: string) { + return `/app/${PLUGIN_ID}${id ? getEditPath(id) : getBasePath()}`; } diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 67d9d5ef64483..f5fba766e60ee 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -13,7 +13,7 @@ "dashboard", "charts" ], - "optionalPlugins": ["embeddable", "usageCollection", "taskManager", "uiActions"], + "optionalPlugins": ["embeddable", "usageCollection", "taskManager", "uiActions", "globalSearch"], "configPath": ["xpack", "lens"], "extraPublicDirs": ["common/constants"], "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable"] diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 63c2a6b9b2f29..24114e2b31518 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -9,29 +9,36 @@ import { Observable } from 'rxjs'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { App } from './app'; +import { LensAppProps, LensAppServices } from './types'; import { EditorFrameInstance } from '../types'; -import { AppMountParameters } from 'kibana/public'; -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; -import { Document, SavedObjectStore } from '../persistence'; +import { Document, DOC_TYPE } from '../persistence'; import { mount } from 'enzyme'; +import { I18nProvider } from '@kbn/i18n/react'; import { SavedObjectSaveModal, checkForDuplicateTitle, } from '../../../../../src/plugins/saved_objects/public'; -import { createMemoryHistory, History } from 'history'; +import { createMemoryHistory } from 'history'; import { + DataPublicPluginStart, esFilters, FilterManager, IFieldType, IIndexPattern, UI_SETTINGS, } from '../../../../../src/plugins/data/public'; -import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; -const dataStartMock = dataPluginMock.createStartContract(); - import { navigationPluginMock } from '../../../../../src/plugins/navigation/public/mocks'; import { TopNavMenuData } from '../../../../../src/plugins/navigation/public'; import { coreMock } from 'src/core/public/mocks'; +import { + LensByValueInput, + LensSavedObjectAttributes, + LensByReferenceInput, +} from '../editor_frame_service/embeddable/embeddable'; +import { SavedObjectReference } from '../../../../../src/core/types'; +import { mockAttributeService } from '../../../../../src/plugins/dashboard/public/mocks'; +import { LensAttributeService } from '../lens_attribute_service'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; jest.mock('../editor_frame_service/editor_frame/expression_helpers'); jest.mock('src/core/public'); @@ -120,39 +127,68 @@ function createMockTimefilter() { } describe('Lens App', () => { - let frame: jest.Mocked; let core: ReturnType; - let instance: ReactWrapper; - - function makeDefaultArgs(): jest.Mocked<{ - editorFrame: EditorFrameInstance; - data: typeof dataStartMock; - navigation: typeof navigationStartMock; - core: typeof core; - storage: Storage; - docId?: string; - docStorage: SavedObjectStore; - redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; - originatingApp: string | undefined; - onAppLeave: AppMountParameters['onAppLeave']; - setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; - history: History; - getAppNameFromId?: (appId: string) => string | undefined; - }> { - return ({ - navigation: navigationStartMock, + let defaultDoc: Document; + let defaultSavedObjectId: string; + + const navMenuItems = { + expectedSaveButton: { emphasize: true, testId: 'lnsApp_saveButton' }, + expectedSaveAsButton: { emphasize: false, testId: 'lnsApp_saveButton' }, + expectedSaveAndReturnButton: { emphasize: true, testId: 'lnsApp_saveAndReturnButton' }, + }; + + function makeAttributeService(): LensAttributeService { + const attributeServiceMock = mockAttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >( + DOC_TYPE, + { + customSaveMethod: jest.fn(), + customUnwrapMethod: jest.fn(), + }, + core + ); + attributeServiceMock.unwrapAttributes = jest.fn().mockResolvedValue(defaultDoc); + attributeServiceMock.wrapAttributes = jest + .fn() + .mockResolvedValue({ savedObjectId: defaultSavedObjectId }); + return attributeServiceMock; + } + + function makeDefaultProps(): jest.Mocked { + return { editorFrame: createMockFrame(), - core: { - ...core, - application: { - ...core.application, - capabilities: { - ...core.application.capabilities, - visualize: { save: true, saveQuery: true, show: true }, - }, + history: createMemoryHistory(), + redirectTo: jest.fn(), + redirectToOrigin: jest.fn(), + onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), + }; + } + + function makeDefaultServices(): jest.Mocked { + return { + http: core.http, + chrome: core.chrome, + overlays: core.overlays, + uiSettings: core.uiSettings, + navigation: navigationStartMock, + notifications: core.notifications, + attributeService: makeAttributeService(), + savedObjectsClient: core.savedObjects.client, + dashboardFeatureFlag: { allowByValueEmbeddables: false }, + getOriginatingAppName: jest.fn(() => 'defaultOriginatingApp'), + application: { + ...core.application, + capabilities: { + ...core.application.capabilities, + visualize: { save: true, saveQuery: true, show: true }, }, + getUrlForApp: jest.fn((appId: string) => `/testbasepath/app/${appId}#/`), }, - data: { + data: ({ query: { filterManager: createMockFilterManager(), timefilter: { @@ -166,38 +202,52 @@ describe('Lens App', () => { return new Promise((resolve) => resolve({ id })); }), }, - }, + } as unknown) as DataPublicPluginStart, storage: { get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), }, - docStorage: { - load: jest.fn(), - save: jest.fn(), - }, - redirectTo: jest.fn((id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => {}), - onAppLeave: jest.fn(), - setHeaderActionMenu: jest.fn(), - history: createMemoryHistory(), - } as unknown) as jest.Mocked<{ - navigation: typeof navigationStartMock; - editorFrame: EditorFrameInstance; - data: typeof dataStartMock; - core: typeof core; - storage: Storage; - docId?: string; - docStorage: SavedObjectStore; - redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; - originatingApp: string | undefined; - onAppLeave: AppMountParameters['onAppLeave']; - setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; - history: History; - getAppNameFromId?: (appId: string) => string | undefined; - }>; + }; + } + + function mountWith({ + props: incomingProps, + services: incomingServices, + }: { + props?: jest.Mocked; + services?: jest.Mocked; + }) { + const props = incomingProps ?? makeDefaultProps(); + const services = incomingServices ?? makeDefaultServices(); + const wrappingComponent: React.FC<{ + children: React.ReactNode; + }> = ({ children }) => { + return ( + + {children} + + ); + }; + const frame = props.editorFrame as ReturnType; + const component = mount(, { wrappingComponent }); + return { component, frame, props, services }; } beforeEach(() => { - frame = createMockFrame(); core = coreMock.createStart({ basePath: '/testbasepath' }); + defaultSavedObjectId = '1234'; + defaultDoc = ({ + savedObjectId: defaultSavedObjectId, + title: 'An extremely cool default document!', + expression: 'definitely a valid expression', + state: { + query: 'kuery', + filters: [{ query: { match_phrase: { src: 'test' } } }], + }, + references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], + } as unknown) as Document; core.uiSettings.get.mockImplementation( jest.fn((type) => { @@ -215,10 +265,7 @@ describe('Lens App', () => { }); it('renders the editor frame', () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - - mount(); + const { frame } = mountWith({}); expect(frame.mount.mock.calls).toMatchInlineSnapshot(` Array [ @@ -248,23 +295,22 @@ describe('Lens App', () => { }); it('clears app filters on load', () => { - const defaultArgs = makeDefaultArgs(); - mount(); - - expect(defaultArgs.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([]); + const { services } = mountWith({}); + expect(services.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([]); }); it('passes global filters to frame', async () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; + const services = makeDefaultServices(); const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; const pinnedFilter = esFilters.buildExistsFilter(pinnedField, indexPattern); - args.data.query.filterManager.getFilters = jest.fn().mockImplementation(() => { + services.data.query.filterManager.getFilters = jest.fn().mockImplementation(() => { return [pinnedFilter]; }); - const component = mount(); + const { component, frame } = mountWith({ services }); + component.update(); + expect(frame.mount).toHaveBeenCalledWith( expect.any(Element), expect.objectContaining({ @@ -275,103 +321,81 @@ describe('Lens App', () => { ); }); - it('sets breadcrumbs when the document title changes', async () => { - const defaultArgs = makeDefaultArgs(); - instance = mount(); - - expect(core.chrome.setBreadcrumbs).toHaveBeenCalledWith([ - { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, - { text: 'Create' }, - ]); + it('displays errors from the frame in a toast', () => { + const { component, frame, services } = mountWith({}); + const onError = frame.mount.mock.calls[0][1].onError; + onError({ message: 'error' }); + component.update(); + expect(services.notifications.toasts.addDanger).toHaveBeenCalled(); + }); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', + describe('breadcrumbs', () => { + const breadcrumbDocSavedObjectId = defaultSavedObjectId; + const breadcrumbDoc = ({ + savedObjectId: breadcrumbDocSavedObjectId, title: 'Daaaaaaadaumching!', state: { query: 'fake query', filters: [], }, references: [], - }); - await act(async () => { - instance.setProps({ docId: '1234' }); - }); + } as unknown) as Document; - expect(defaultArgs.core.chrome.setBreadcrumbs).toHaveBeenCalledWith([ - { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, - { text: 'Daaaaaaadaumching!' }, - ]); - }); + it('sets breadcrumbs when the document title changes', async () => { + const { component, services } = mountWith({}); - it('adds to the recently viewed list on load', async () => { - const defaultArgs = makeDefaultArgs(); - instance = mount(); + expect(services.chrome.setBreadcrumbs).toHaveBeenCalledWith([ + { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, + { text: 'Create' }, + ]); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'Daaaaaaadaumching!', - state: { - query: 'fake query', - filters: [], - }, - references: [], - }); - await act(async () => { - instance.setProps({ docId: '1234' }); + services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(breadcrumbDoc); + await act(async () => { + component.setProps({ initialInput: { savedObjectId: breadcrumbDocSavedObjectId } }); + }); + + expect(services.chrome.setBreadcrumbs).toHaveBeenCalledWith([ + { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, + { text: 'Daaaaaaadaumching!' }, + ]); }); - expect(defaultArgs.core.chrome.recentlyAccessed.add).toHaveBeenCalledWith( - '/app/lens#/edit/1234', - 'Daaaaaaadaumching!', - '1234' - ); - }); - it('sets originatingApp breadcrumb when the document title changes', async () => { - const defaultArgs = makeDefaultArgs(); - defaultArgs.originatingApp = 'ultraCoolDashboard'; - defaultArgs.getAppNameFromId = () => 'The Coolest Container Ever Made'; - instance = mount(); + it('sets originatingApp breadcrumb when the document title changes', async () => { + const props = makeDefaultProps(); + const services = makeDefaultServices(); + props.incomingState = { originatingApp: 'coolContainer' }; + services.getOriginatingAppName = jest.fn(() => 'The Coolest Container Ever Made'); + const { component } = mountWith({ props, services }); + + expect(services.chrome.setBreadcrumbs).toHaveBeenCalledWith([ + { text: 'The Coolest Container Ever Made', onClick: expect.anything() }, + { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, + { text: 'Create' }, + ]); - expect(core.chrome.setBreadcrumbs).toHaveBeenCalledWith([ - { text: 'The Coolest Container Ever Made', onClick: expect.anything() }, - { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, - { text: 'Create' }, - ]); + services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(breadcrumbDoc); + await act(async () => { + component.setProps({ initialInput: { savedObjectId: breadcrumbDocSavedObjectId } }); + }); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'Daaaaaaadaumching!', - state: { - query: 'fake query', - filters: [], - }, - references: [], - }); - await act(async () => { - instance.setProps({ docId: '1234' }); + expect(services.chrome.setBreadcrumbs).toHaveBeenCalledWith([ + { text: 'The Coolest Container Ever Made', onClick: expect.anything() }, + { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, + { text: 'Daaaaaaadaumching!' }, + ]); }); - - expect(defaultArgs.core.chrome.setBreadcrumbs).toHaveBeenCalledWith([ - { text: 'The Coolest Container Ever Made', onClick: expect.anything() }, - { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, - { text: 'Daaaaaaadaumching!' }, - ]); }); describe('persistence', () => { - it('does not load a document if there is no document id', () => { - const args = makeDefaultArgs(); - - mount(); - - expect(args.docStorage.load).not.toHaveBeenCalled(); + it('does not load a document if there is no initial input', () => { + const { services } = mountWith({}); + expect(services.attributeService.unwrapAttributes).not.toHaveBeenCalled(); }); - it('loads a document and uses query and filters if there is a document id', async () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - (args.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', + it('loads a document and uses query and filters if initial input is provided', async () => { + const { component, frame, services } = mountWith({}); + services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({ + savedObjectId: defaultSavedObjectId, state: { query: 'fake query', filters: [{ query: { match_phrase: { src: 'test' } } }], @@ -379,15 +403,15 @@ describe('Lens App', () => { references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], }); - instance = mount(); - await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - expect(args.docStorage.load).toHaveBeenCalledWith('1234'); - expect(args.data.indexPatterns.get).toHaveBeenCalledWith('1'); - expect(args.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([ + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledWith({ + savedObjectId: defaultSavedObjectId, + }); + expect(services.data.indexPatterns.get).toHaveBeenCalledWith('1'); + expect(services.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([ { query: { match_phrase: { src: 'test' } } }, ]); expect(TopNavMenu).toHaveBeenCalledWith( @@ -401,7 +425,7 @@ describe('Lens App', () => { expect.any(Element), expect.objectContaining({ doc: expect.objectContaining({ - id: '1234', + savedObjectId: defaultSavedObjectId, state: expect.objectContaining({ query: 'fake query', filters: [{ query: { match_phrase: { src: 'test' } } }], @@ -412,65 +436,59 @@ describe('Lens App', () => { }); it('does not load documents on sequential renders unless the id changes', async () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - (args.docStorage.load as jest.Mock).mockResolvedValue({ id: '1234' }); + const { services, component } = mountWith({}); - instance = mount(); await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - - expect(args.docStorage.load).toHaveBeenCalledTimes(1); + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledTimes(1); await act(async () => { - instance.setProps({ docId: '9876' }); + component.setProps({ initialInput: { savedObjectId: '5678' } }); }); - expect(args.docStorage.load).toHaveBeenCalledTimes(2); + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledTimes(2); }); it('handles document load errors', async () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - (args.docStorage.load as jest.Mock).mockRejectedValue('failed to load'); - - instance = mount(); + const services = makeDefaultServices(); + services.attributeService.unwrapAttributes = jest.fn().mockRejectedValue('failed to load'); + const { component, props } = mountWith({ services }); await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - expect(args.docStorage.load).toHaveBeenCalledWith('1234'); - expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled(); - expect(args.redirectTo).toHaveBeenCalled(); + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledWith({ + savedObjectId: defaultSavedObjectId, + }); + expect(services.notifications.toasts.addDanger).toHaveBeenCalled(); + expect(props.redirectTo).toHaveBeenCalled(); }); - describe('save button', () => { + it('adds to the recently accessed list on load', async () => { + const { component, services } = mountWith({}); + + await act(async () => { + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); + }); + expect(services.chrome.recentlyAccessed.add).toHaveBeenCalledWith( + '/app/lens#/edit/1234', + 'An extremely cool default document!', + '1234' + ); + }); + + describe('save buttons', () => { interface SaveProps { newCopyOnSave: boolean; returnToOrigin?: boolean; newTitle: string; } - let defaultArgs: ReturnType; - - beforeEach(() => { - defaultArgs = makeDefaultArgs(); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'My cool doc', - expression: 'valid expression', - state: { - query: 'kuery', - }, - } as jest.ResolvedValue); - }); - function getButton(inst: ReactWrapper): TopNavMenuData { return (inst .find('[data-test-subj="lnsApp_topNav"]') @@ -495,135 +513,195 @@ describe('Lens App', () => { filters: [], }, }, - initialDocId, + initialSavedObjectId, ...saveProps }: SaveProps & { lastKnownDoc?: object; - initialDocId?: string; + initialSavedObjectId?: string; }) { - const args = { - ...defaultArgs, - docId: initialDocId, + const props = { + ...makeDefaultProps(), + initialInput: initialSavedObjectId + ? { savedObjectId: initialSavedObjectId, id: '5678' } + : undefined, }; - args.editorFrame = frame; - (args.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', + + const services = makeDefaultServices(); + services.attributeService.wrapAttributes = jest + .fn() + .mockImplementation(async ({ savedObjectId }) => ({ + savedObjectId: savedObjectId || 'aaa', + })); + services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({ + savedObjectId: initialSavedObjectId ?? 'aaa', references: [], state: { query: 'fake query', filters: [], }, - }); - (args.docStorage.save as jest.Mock).mockImplementation(async ({ id }) => ({ - id: id || 'aaa', - })); + } as jest.ResolvedValue); + let frame: jest.Mocked = {} as jest.Mocked; + let component: ReactWrapper = {} as ReactWrapper; await act(async () => { - instance = mount(); + const { frame: newFrame, component: newComponent } = mountWith({ services, props }); + frame = newFrame; + component = newComponent; }); - if (initialDocId) { - expect(args.docStorage.load).toHaveBeenCalledTimes(1); + if (initialSavedObjectId) { + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledTimes(1); } else { - expect(args.docStorage.load).not.toHaveBeenCalled(); + expect(services.attributeService.unwrapAttributes).not.toHaveBeenCalled(); } const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => onChange({ filterableIndexPatterns: [], - doc: { id: initialDocId, ...lastKnownDoc } as Document, + doc: { savedObjectId: initialSavedObjectId, ...lastKnownDoc } as Document, isSaveable: true, }) ); - - instance.update(); - - expect(getButton(instance).disableButton).toEqual(false); - + component.update(); + expect(getButton(component).disableButton).toEqual(false); await act(async () => { - testSave(instance, { ...saveProps }); + testSave(component, { ...saveProps }); }); - - return { args, instance }; + return { props, services, component, frame }; } it('shows a disabled save button when the user does not have permissions', async () => { - const args = defaultArgs; - args.core.application = { - ...args.core.application, + const services = makeDefaultServices(); + services.application = { + ...services.application, capabilities: { - ...args.core.application.capabilities, + ...services.application.capabilities, visualize: { save: false, saveQuery: false, show: true }, }, }; - args.editorFrame = frame; - - instance = mount(); - - expect(getButton(instance).disableButton).toEqual(true); - + const { component, frame } = mountWith({ services }); + expect(getButton(component).disableButton).toEqual(true); const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], - doc: ({ id: 'will save this' } as unknown) as Document, + doc: ({ savedObjectId: 'will save this' } as unknown) as Document, isSaveable: true, }) ); - instance.update(); - expect(getButton(instance).disableButton).toEqual(true); + component.update(); + expect(getButton(component).disableButton).toEqual(true); }); - it('shows a save button that is enabled when the frame has provided its state', async () => { - const args = defaultArgs; - args.editorFrame = frame; - - instance = mount(); - - expect(getButton(instance).disableButton).toEqual(true); - + it('shows a save button that is enabled when the frame has provided its state and does not show save and return or save as', async () => { + const { component, frame } = mountWith({}); + expect(getButton(component).disableButton).toEqual(true); const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], - doc: ({ id: 'will save this' } as unknown) as Document, + doc: ({ savedObjectId: 'will save this' } as unknown) as Document, isSaveable: true, }) ); - instance.update(); + component.update(); + expect(getButton(component).disableButton).toEqual(false); - expect(getButton(instance).disableButton).toEqual(false); + await act(async () => { + const topNavMenuConfig = component.find(TopNavMenu).prop('config'); + expect(topNavMenuConfig).not.toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) + ); + expect(topNavMenuConfig).not.toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAsButton) + ); + expect(topNavMenuConfig).toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveButton) + ); + }); + }); + + it('Shows Save and Return and Save As buttons in create by value mode', async () => { + const props = makeDefaultProps(); + const services = makeDefaultServices(); + services.dashboardFeatureFlag = { allowByValueEmbeddables: true }; + props.incomingState = { + originatingApp: 'ultraDashboard', + valueInput: { + id: 'whatchaGonnaDoWith', + attributes: { + title: + 'whatcha gonna do with all these references? All these references in your value Input', + references: [] as SavedObjectReference[], + }, + } as LensByValueInput, + }; + + const { component } = mountWith({ props, services }); + + await act(async () => { + const topNavMenuConfig = component.find(TopNavMenu).prop('config'); + expect(topNavMenuConfig).toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) + ); + expect(topNavMenuConfig).toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAsButton) + ); + expect(topNavMenuConfig).not.toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveButton) + ); + }); + }); + + it('Shows Save and Return and Save As buttons in edit by reference mode', async () => { + const props = makeDefaultProps(); + props.initialInput = { savedObjectId: defaultSavedObjectId, id: '5678' }; + props.incomingState = { + originatingApp: 'ultraDashboard', + }; + + const { component } = mountWith({ props }); + + await act(async () => { + const topNavMenuConfig = component.find(TopNavMenu).prop('config'); + expect(topNavMenuConfig).toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) + ); + expect(topNavMenuConfig).toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAsButton) + ); + expect(topNavMenuConfig).not.toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveButton) + ); + }); }); it('saves new docs', async () => { - const { args, instance: inst } = await save({ - initialDocId: undefined, + const { props, services } = await save({ + initialSavedObjectId: undefined, newCopyOnSave: false, newTitle: 'hello there', }); - - expect(args.docStorage.save).toHaveBeenCalledWith( + expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( expect.objectContaining({ - id: undefined, + savedObjectId: undefined, title: 'hello there', - }) + }), + true, + undefined ); - - expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, true); - - inst.setProps({ docId: 'aaa' }); - - expect(args.docStorage.load).not.toHaveBeenCalled(); + expect(props.redirectTo).toHaveBeenCalledWith('aaa'); }); - it('adds to the recently viewed list on save', async () => { - const { args } = await save({ - initialDocId: undefined, + it('adds to the recently accessed list on save', async () => { + const { services } = await save({ + initialSavedObjectId: undefined, newCopyOnSave: false, newTitle: 'hello there', }); - expect(args.core.chrome.recentlyAccessed.add).toHaveBeenCalledWith( + expect(services.chrome.recentlyAccessed.add).toHaveBeenCalledWith( '/app/lens#/edit/aaa', 'hello there', 'aaa' @@ -631,54 +709,53 @@ describe('Lens App', () => { }); it('saves the latest doc as a copy', async () => { - const { args, instance: inst } = await save({ - initialDocId: '1234', + const { props, services, component } = await save({ + initialSavedObjectId: defaultSavedObjectId, newCopyOnSave: true, newTitle: 'hello there', }); - - expect(args.docStorage.save).toHaveBeenCalledWith( + expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( expect.objectContaining({ - id: undefined, + savedObjectId: undefined, title: 'hello there', - }) + }), + true, + undefined ); - - expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, true); - - inst.setProps({ docId: 'aaa' }); - - expect(args.docStorage.load).toHaveBeenCalledTimes(1); + expect(props.redirectTo).toHaveBeenCalledWith('aaa'); + await act(async () => { + component.setProps({ initialInput: { savedObjectId: 'aaa' } }); + }); + expect(services.attributeService.wrapAttributes).toHaveBeenCalledTimes(1); }); it('saves existing docs', async () => { - const { args, instance: inst } = await save({ - initialDocId: '1234', + const { props, services, component } = await save({ + initialSavedObjectId: defaultSavedObjectId, newCopyOnSave: false, newTitle: 'hello there', }); - - expect(args.docStorage.save).toHaveBeenCalledWith( + expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( expect.objectContaining({ - id: '1234', + savedObjectId: defaultSavedObjectId, title: 'hello there', - }) + }), + true, + { id: '5678', savedObjectId: defaultSavedObjectId } ); - - expect(args.redirectTo).not.toHaveBeenCalled(); - - inst.setProps({ docId: '1234' }); - - expect(args.docStorage.load).toHaveBeenCalledTimes(1); + expect(props.redirectTo).not.toHaveBeenCalled(); + await act(async () => { + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); + }); + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledTimes(1); }); it('handles save failure by showing a warning, but still allows another save', async () => { - const args = defaultArgs; - args.editorFrame = frame; - (args.docStorage.save as jest.Mock).mockRejectedValue({ message: 'failed' }); - - instance = mount(); - + const services = makeDefaultServices(); + services.attributeService.wrapAttributes = jest + .fn() + .mockRejectedValue({ message: 'failed' }); + const { component, props, frame } = mountWith({ services }); const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ @@ -687,51 +764,48 @@ describe('Lens App', () => { isSaveable: true, }) ); - - instance.update(); + component.update(); await act(async () => { - testSave(instance, { newCopyOnSave: false, newTitle: 'hello there' }); + testSave(component, { newCopyOnSave: false, newTitle: 'hello there' }); }); - - expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled(); - expect(args.redirectTo).not.toHaveBeenCalled(); - - expect(getButton(instance).disableButton).toEqual(false); + expect(services.notifications.toasts.addDanger).toHaveBeenCalled(); + expect(props.redirectTo).not.toHaveBeenCalled(); + expect(getButton(component).disableButton).toEqual(false); }); it('saves new doc and redirects to originating app', async () => { - const { args } = await save({ - initialDocId: undefined, + const { props, services } = await save({ + initialSavedObjectId: undefined, returnToOrigin: true, newCopyOnSave: false, newTitle: 'hello there', }); - - expect(args.docStorage.save).toHaveBeenCalledWith( + expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( expect.objectContaining({ - id: undefined, + savedObjectId: undefined, title: 'hello there', - }) + }), + true, + undefined ); - - expect(args.redirectTo).toHaveBeenCalledWith('aaa', true, true); + expect(props.redirectToOrigin).toHaveBeenCalledWith({ + input: { savedObjectId: 'aaa' }, + isCopied: false, + }); }); it('saves app filters and does not save pinned filters', async () => { const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; const field = ({ name: 'myfield' } as unknown) as IFieldType; const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; - const unpinned = esFilters.buildExistsFilter(field, indexPattern); const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); - await act(async () => { FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); }); - - const { args } = await save({ - initialDocId: '1234', + const { services } = await save({ + initialSavedObjectId: defaultSavedObjectId, newCopyOnSave: false, newTitle: 'hello there2', lastKnownDoc: { @@ -741,42 +815,42 @@ describe('Lens App', () => { }, }, }); - - expect(args.docStorage.save).toHaveBeenCalledWith({ - id: '1234', - title: 'hello there2', - expression: 'kibana 3', - state: { - filters: [unpinned], + expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( + { + savedObjectId: defaultSavedObjectId, + title: 'hello there2', + expression: 'kibana 3', + state: { + filters: [unpinned], + }, }, - }); + true, + { id: '5678', savedObjectId: defaultSavedObjectId } + ); }); it('checks for duplicate title before saving', async () => { - const args = defaultArgs; - args.editorFrame = frame; - (args.docStorage.save as jest.Mock).mockReturnValue(Promise.resolve({ id: '123' })); - - instance = mount(); - + const services = makeDefaultServices(); + services.attributeService.wrapAttributes = jest + .fn() + .mockReturnValue(Promise.resolve({ savedObjectId: '123' })); + const { component, frame } = mountWith({ services }); const onChange = frame.mount.mock.calls[0][1].onChange; await act(async () => onChange({ filterableIndexPatterns: [], - doc: ({ id: '123' } as unknown) as Document, + doc: ({ savedObjectId: '123' } as unknown) as Document, isSaveable: true, }) ); - instance.update(); + component.update(); await act(async () => { - getButton(instance).run(instance.getDOMNode()); + getButton(component).run(component.getDOMNode()); }); - instance.update(); - + component.update(); const onTitleDuplicate = jest.fn(); - await act(async () => { - instance.find(SavedObjectSaveModal).prop('onSave')({ + component.find(SavedObjectSaveModal).prop('onSave')({ onTitleDuplicate, isTitleDuplicateConfirmed: false, newCopyOnSave: false, @@ -784,9 +858,8 @@ describe('Lens App', () => { newTitle: 'test', }); }); - expect(checkForDuplicateTitle).toHaveBeenCalledWith( - expect.objectContaining({ id: '123' }), + expect.objectContaining({ savedObjectId: '123' }), false, onTitleDuplicate, expect.anything() @@ -794,11 +867,7 @@ describe('Lens App', () => { }); it('does not show the copy button on first save', async () => { - const args = defaultArgs; - args.editorFrame = frame; - - instance = mount(); - + const { component, frame } = mountWith({}); const onChange = frame.mount.mock.calls[0][1].onChange; await act(async () => onChange({ @@ -807,36 +876,17 @@ describe('Lens App', () => { isSaveable: true, }) ); - instance.update(); - await act(async () => getButton(instance).run(instance.getDOMNode())); - instance.update(); - - expect(instance.find(SavedObjectSaveModal).prop('showCopyOnSave')).toEqual(false); + component.update(); + await act(async () => getButton(component).run(component.getDOMNode())); + component.update(); + expect(component.find(SavedObjectSaveModal).prop('showCopyOnSave')).toEqual(false); }); }); }); describe('query bar state management', () => { - let defaultArgs: ReturnType; - - beforeEach(() => { - defaultArgs = makeDefaultArgs(); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'My cool doc', - expression: 'valid expression', - state: { - query: 'kuery', - }, - } as jest.ResolvedValue); - }); - it('uses the default time and query language settings', () => { - const args = defaultArgs; - args.editorFrame = frame; - - mount(); - + const { frame } = mountWith({}); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: '', language: 'kuery' }, @@ -855,20 +905,14 @@ describe('Lens App', () => { }); it('updates the index patterns when the editor frame is changed', async () => { - const args = defaultArgs; - args.editorFrame = frame; - - instance = mount(); - + const { component, frame } = mountWith({}); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ indexPatterns: [], }), {} ); - const onChange = frame.mount.mock.calls[0][1].onChange; - await act(async () => { onChange({ filterableIndexPatterns: ['1'], @@ -876,18 +920,14 @@ describe('Lens App', () => { isSaveable: true, }); }); - - instance.update(); - + component.update(); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ indexPatterns: [{ id: '1' }], }), {} ); - // Do it again to verify that the dirty checking is done right - await act(async () => { onChange({ filterableIndexPatterns: ['2'], @@ -895,9 +935,7 @@ describe('Lens App', () => { isSaveable: true, }); }); - - instance.update(); - + component.update(); expect(TopNavMenu).toHaveBeenLastCalledWith( expect.objectContaining({ indexPatterns: [{ id: '2' }], @@ -905,21 +943,16 @@ describe('Lens App', () => { {} ); }); - it('updates the editor frame when the user changes query or time in the search bar', () => { - const args = defaultArgs; - args.editorFrame = frame; - - instance = mount(); + it('updates the editor frame when the user changes query or time in the search bar', () => { + const { component, frame } = mountWith({}); act(() => - instance.find(TopNavMenu).prop('onQuerySubmit')!({ + component.find(TopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) ); - - instance.update(); - + component.update(); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: 'new', language: 'lucene' }, @@ -938,19 +971,15 @@ describe('Lens App', () => { }); it('updates the filters when the user changes them', () => { - const args = defaultArgs; - args.editorFrame = frame; - - instance = mount(); + const { component, frame, services } = mountWith({}); const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; const field = ({ name: 'myfield' } as unknown) as IFieldType; - act(() => - args.data.query.filterManager.setFilters([esFilters.buildExistsFilter(field, indexPattern)]) + services.data.query.filterManager.setFilters([ + esFilters.buildExistsFilter(field, indexPattern), + ]) ); - - instance.update(); - + component.update(); expect(frame.mount).toHaveBeenCalledWith( expect.any(Element), expect.objectContaining({ @@ -962,17 +991,15 @@ describe('Lens App', () => { describe('saved query handling', () => { it('does not allow saving when the user is missing the saveQuery permission', () => { - const args = makeDefaultArgs(); - args.core.application = { - ...args.core.application, + const services = makeDefaultServices(); + services.application = { + ...services.application, capabilities: { - ...args.core.application.capabilities, + ...services.application.capabilities, visualize: { save: false, saveQuery: false, show: true }, }, }; - - mount(); - + mountWith({ services }); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showSaveQuery: false }), {} @@ -980,11 +1007,7 @@ describe('Lens App', () => { }); it('persists the saved query ID when the query is saved', () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - - instance = mount(); - + const { component } = mountWith({}); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showSaveQuery: true, @@ -995,9 +1018,8 @@ describe('Lens App', () => { }), {} ); - act(() => { - instance.find(TopNavMenu).prop('onSaved')!({ + component.find(TopNavMenu).prop('onSaved')!({ id: '1', attributes: { title: '', @@ -1006,7 +1028,6 @@ describe('Lens App', () => { }, }); }); - expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ savedQuery: { @@ -1023,13 +1044,9 @@ describe('Lens App', () => { }); it('changes the saved query ID when the query is updated', () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - - instance = mount(); - + const { component } = mountWith({}); act(() => { - instance.find(TopNavMenu).prop('onSaved')!({ + component.find(TopNavMenu).prop('onSaved')!({ id: '1', attributes: { title: '', @@ -1038,9 +1055,8 @@ describe('Lens App', () => { }, }); }); - act(() => { - instance.find(TopNavMenu).prop('onSavedQueryUpdated')!({ + component.find(TopNavMenu).prop('onSavedQueryUpdated')!({ id: '2', attributes: { title: 'new title', @@ -1049,7 +1065,6 @@ describe('Lens App', () => { }, }); }); - expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ savedQuery: { @@ -1066,32 +1081,23 @@ describe('Lens App', () => { }); it('clears all existing unpinned filters when the active saved query is cleared', () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - - instance = mount(); - + const { component, frame, services } = mountWith({}); act(() => - instance.find(TopNavMenu).prop('onQuerySubmit')!({ + component.find(TopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) ); - const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; const field = ({ name: 'myfield' } as unknown) as IFieldType; const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; - const unpinned = esFilters.buildExistsFilter(field, indexPattern); const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); - - act(() => args.data.query.filterManager.setFilters([pinned, unpinned])); - instance.update(); - - act(() => instance.find(TopNavMenu).prop('onClearSavedQuery')!()); - instance.update(); - + act(() => services.data.query.filterManager.setFilters([pinned, unpinned])); + component.update(); + act(() => component.find(TopNavMenu).prop('onClearSavedQuery')!()); + component.update(); expect(frame.mount).toHaveBeenLastCalledWith( expect.any(Element), expect.objectContaining({ @@ -1101,191 +1107,127 @@ describe('Lens App', () => { }); }); - it('displays errors from the frame in a toast', () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - - instance = mount(); - - const onError = frame.mount.mock.calls[0][1].onError; - onError({ message: 'error' }); - - instance.update(); - - expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled(); - }); - describe('showing a confirm message when leaving', () => { - let defaultArgs: ReturnType; let defaultLeave: jest.Mock; let confirmLeave: jest.Mock; beforeEach(() => { - defaultArgs = makeDefaultArgs(); defaultLeave = jest.fn(); confirmLeave = jest.fn(); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'My cool doc', - state: { - query: 'kuery', - filters: [], - }, - references: [], - } as jest.ResolvedValue); }); it('should not show a confirm message if there is no expression to save', () => { - instance = mount(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + const { props } = mountWith({}); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(defaultLeave).toHaveBeenCalled(); expect(confirmLeave).not.toHaveBeenCalled(); }); it('does not confirm if the user is missing save permissions', () => { - const args = defaultArgs; - args.core.application = { - ...args.core.application, + const services = makeDefaultServices(); + services.application = { + ...services.application, capabilities: { - ...args.core.application.capabilities, + ...services.application.capabilities, visualize: { save: false, saveQuery: false, show: true }, }, }; - args.editorFrame = frame; - - instance = mount(); - + const { component, frame, props } = mountWith({ services }); const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], doc: ({ - id: undefined, - + savedObjectId: undefined, references: [], } as unknown) as Document, isSaveable: true, }) ); - instance.update(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + component.update(); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(defaultLeave).toHaveBeenCalled(); expect(confirmLeave).not.toHaveBeenCalled(); }); it('should confirm when leaving with an unsaved doc', () => { - defaultArgs.editorFrame = frame; - instance = mount(); - + const { component, frame, props } = mountWith({}); const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], - doc: ({ id: undefined, state: {} } as unknown) as Document, + doc: ({ savedObjectId: undefined, state: {} } as unknown) as Document, isSaveable: true, }) ); - instance.update(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + component.update(); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(confirmLeave).toHaveBeenCalled(); expect(defaultLeave).not.toHaveBeenCalled(); }); it('should confirm when leaving with unsaved changes to an existing doc', async () => { - defaultArgs.editorFrame = frame; - instance = mount(); + const { component, frame, props } = mountWith({}); await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], doc: ({ - id: '1234', - + savedObjectId: defaultSavedObjectId, references: [], } as unknown) as Document, isSaveable: true, }) ); - instance.update(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + component.update(); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(confirmLeave).toHaveBeenCalled(); expect(defaultLeave).not.toHaveBeenCalled(); }); it('should not confirm when changes are saved', async () => { - defaultArgs.editorFrame = frame; - instance = mount(); + const { component, frame, props } = mountWith({}); await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], - doc: ({ - id: '1234', - title: 'My cool doc', - references: [], - state: { - query: 'kuery', - filters: [], - }, - } as unknown) as Document, + doc: defaultDoc, isSaveable: true, }) ); - instance.update(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + component.update(); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(defaultLeave).toHaveBeenCalled(); expect(confirmLeave).not.toHaveBeenCalled(); }); it('should confirm when the latest doc is invalid', async () => { - defaultArgs.editorFrame = frame; - instance = mount(); + const { component, frame, props } = mountWith({}); await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], - doc: ({ id: '1234', references: [] } as unknown) as Document, + doc: ({ savedObjectId: defaultSavedObjectId, references: [] } as unknown) as Document, isSaveable: true, }) ); - instance.update(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + component.update(); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(confirmLeave).toHaveBeenCalled(); expect(defaultLeave).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index bfdf4ceaaabd3..d2ccbe0cb2fee 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -6,108 +6,82 @@ import _ from 'lodash'; import React, { useState, useEffect, useCallback } from 'react'; -import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; -import { AppMountContext, AppMountParameters, NotificationsStart } from 'kibana/public'; -import { History } from 'history'; +import { NotificationsStart } from 'kibana/public'; import { EuiBreadcrumb } from '@elastic/eui'; -import { - Query, - DataPublicPluginStart, - syncQueryStateWithUrl, -} from '../../../../../src/plugins/data/public'; import { createKbnUrlStateStorage, - IStorageWrapper, withNotifyOnErrors, } from '../../../../../src/plugins/kibana_utils/public'; -import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { - SavedObjectSaveModalOrigin, OnSaveProps, checkForDuplicateTitle, + SavedObjectSaveModalOrigin, } from '../../../../../src/plugins/saved_objects/public'; -import { Document, SavedObjectStore, injectFilterReferences } from '../persistence'; -import { EditorFrameInstance } from '../types'; +import { injectFilterReferences } from '../persistence'; import { NativeRenderer } from '../native_renderer'; import { trackUiEvent } from '../lens_ui_telemetry'; import { esFilters, - Filter, IndexPattern as IndexPatternInstance, IndexPatternsContract, - SavedQuery, + syncQueryStateWithUrl, } from '../../../../../src/plugins/data/public'; -import { getFullPath } from '../../common'; - -interface State { - indicateNoData: boolean; - isLoading: boolean; - isSaveModalVisible: boolean; - indexPatternsForTopNav: IndexPatternInstance[]; - originatingApp?: string; - persistedDoc?: Document; - lastKnownDoc?: Document; - - // Properties needed to interface with TopNav - dateRange: { - fromDate: string; - toDate: string; - }; - query: Query; - filters: Filter[]; - savedQuery?: SavedQuery; - isSaveable: boolean; -} +import { LENS_EMBEDDABLE_TYPE, getFullPath } from '../../common'; +import { LensAppProps, LensAppServices, LensAppState } from './types'; +import { getLensTopNavConfig } from './lens_top_nav'; +import { + LensByReferenceInput, + LensEmbeddableInput, +} from '../editor_frame_service/embeddable/embeddable'; export function App({ - editorFrame, - data, - core, - storage, - docId, - docStorage, - redirectTo, - originatingApp, - navigation, + history, onAppLeave, + redirectTo, + editorFrame, + initialInput, + incomingState, + redirectToOrigin, setHeaderActionMenu, - history, - getAppNameFromId, -}: { - editorFrame: EditorFrameInstance; - data: DataPublicPluginStart; - navigation: NavigationPublicPluginStart; - core: AppMountContext['core']; - storage: IStorageWrapper; - docId?: string; - docStorage: SavedObjectStore; - redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; - originatingApp?: string | undefined; - onAppLeave: AppMountParameters['onAppLeave']; - setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; - history: History; - getAppNameFromId?: (appId: string) => string | undefined; -}) { - const [state, setState] = useState(() => { +}: LensAppProps) { + const { + data, + chrome, + overlays, + navigation, + uiSettings, + application, + notifications, + attributeService, + savedObjectsClient, + getOriginatingAppName, + + // Temporarily required until the 'by value' paradigm is default. + dashboardFeatureFlag, + } = useKibana().services; + + const [state, setState] = useState(() => { const currentRange = data.query.timefilter.timefilter.getTime(); return { - isLoading: !!docId, - isSaveModalVisible: false, - indexPatternsForTopNav: [], query: data.query.queryString.getDefaultQuery(), + filters: data.query.filterManager.getFilters(), + isLoading: Boolean(initialInput), + indexPatternsForTopNav: [], dateRange: { fromDate: currentRange.from, toDate: currentRange.to, }, - originatingApp, - filters: data.query.filterManager.getFilters(), + isLinkedToOriginatingApp: Boolean(incomingState?.originatingApp), + isSaveModalVisible: false, indicateNoData: false, isSaveable: false, }; }); + const { lastKnownDoc } = state; + const showNoDataPopover = useCallback(() => { setState((prevState) => ({ ...prevState, indicateNoData: true })); }, [setState]); @@ -125,9 +99,44 @@ export function App({ state.indexPatternsForTopNav, ]); - const { lastKnownDoc } = state; + const onError = useCallback( + (e: { message: string }) => + notifications.toasts.addDanger({ + title: e.message, + }), + [notifications.toasts] + ); + + const getLastKnownDocWithoutPinnedFilters = useCallback( + function () { + if (!lastKnownDoc) return undefined; + const [pinnedFilters, appFilters] = _.partition( + injectFilterReferences(lastKnownDoc.state?.filters || [], lastKnownDoc.references), + esFilters.isFilterPinned + ); + return pinnedFilters?.length + ? { + ...lastKnownDoc, + state: { + ...lastKnownDoc.state, + filters: appFilters, + }, + } + : lastKnownDoc; + }, + [lastKnownDoc] + ); - const savingPermitted = state.isSaveable && core.application.capabilities.visualize.save; + const getIsByValueMode = useCallback( + () => + Boolean( + // Temporarily required until the 'by value' paradigm is default. + dashboardFeatureFlag.allowByValueEmbeddables && + state.isLinkedToOriginatingApp && + !(initialInput as LensByReferenceInput)?.savedObjectId + ), + [dashboardFeatureFlag.allowByValueEmbeddables, state.isLinkedToOriginatingApp, initialInput] + ); useEffect(() => { // Clear app-specific filters when navigating to Lens. Necessary because Lens @@ -156,8 +165,8 @@ export function App({ const kbnUrlStateStorage = createKbnUrlStateStorage({ history, - useHash: core.uiSettings.get('state:storeInSessionStorage'), - ...withNotifyOnErrors(core.notifications.toasts), + useHash: uiSettings.get('state:storeInSessionStorage'), + ...withNotifyOnErrors(notifications.toasts), }); const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( data.query, @@ -172,38 +181,18 @@ export function App({ }, [ data.query.filterManager, data.query.timefilter.timefilter, - core.notifications.toasts, - core.uiSettings, + notifications.toasts, + uiSettings, data.query, history, ]); - const getLastKnownDocWithoutPinnedFilters = useCallback( - function () { - if (!lastKnownDoc) return undefined; - const [pinnedFilters, appFilters] = _.partition( - injectFilterReferences(lastKnownDoc.state?.filters || [], lastKnownDoc.references), - esFilters.isFilterPinned - ); - return pinnedFilters?.length - ? { - ...lastKnownDoc, - state: { - ...lastKnownDoc.state, - filters: appFilters, - }, - } - : lastKnownDoc; - }, - [lastKnownDoc] - ); - useEffect(() => { onAppLeave((actions) => { // Confirm when the user has made any changes to an existing doc // or when the user has configured something without saving if ( - core.application.capabilities.visualize.save && + application.capabilities.visualize.save && !_.isEqual(state.persistedDoc?.state, getLastKnownDocWithoutPinnedFilters()?.state) && (state.isSaveable || state.persistedDoc) ) { @@ -220,379 +209,430 @@ export function App({ } }); }, [ - lastKnownDoc, onAppLeave, - state.persistedDoc, + lastKnownDoc, state.isSaveable, - core.application.capabilities.visualize.save, + state.persistedDoc, getLastKnownDocWithoutPinnedFilters, + application.capabilities.visualize.save, ]); // Sync Kibana breadcrumbs any time the saved document's title changes useEffect(() => { - core.chrome.setBreadcrumbs([ - ...(originatingApp && getAppNameFromId - ? [ - { - onClick: (e) => { - core.application.navigateToApp(originatingApp); - }, - text: getAppNameFromId(originatingApp), - } as EuiBreadcrumb, - ] - : []), - { - href: core.http.basePath.prepend(`/app/visualize#/`), + const isByValueMode = getIsByValueMode(); + const breadcrumbs: EuiBreadcrumb[] = []; + if (state.isLinkedToOriginatingApp && getOriginatingAppName() && redirectToOrigin) { + breadcrumbs.push({ + onClick: () => { + redirectToOrigin(); + }, + text: getOriginatingAppName(), + }); + } + if (!isByValueMode) { + breadcrumbs.push({ + href: application.getUrlForApp('visualize'), onClick: (e) => { - core.application.navigateToApp('visualize', { path: '/' }); + application.navigateToApp('visualize', { path: '/' }); e.preventDefault(); }, text: i18n.translate('xpack.lens.breadcrumbsTitle', { defaultMessage: 'Visualize', }), - }, - { - text: state.persistedDoc - ? state.persistedDoc.title - : i18n.translate('xpack.lens.breadcrumbsCreate', { defaultMessage: 'Create' }), - }, - ]); + }); + } + let currentDocTitle = i18n.translate('xpack.lens.breadcrumbsCreate', { + defaultMessage: 'Create', + }); + if (state.persistedDoc) { + currentDocTitle = isByValueMode + ? i18n.translate('xpack.lens.breadcrumbsByValue', { defaultMessage: 'Edit visualization' }) + : state.persistedDoc.title; + } + breadcrumbs.push({ text: currentDocTitle }); + chrome.setBreadcrumbs(breadcrumbs); }, [ - core.application, - core.chrome, - core.http.basePath, + dashboardFeatureFlag.allowByValueEmbeddables, + state.isLinkedToOriginatingApp, + getOriginatingAppName, state.persistedDoc, - originatingApp, - redirectTo, - getAppNameFromId, + redirectToOrigin, + getIsByValueMode, + initialInput, + application, + chrome, ]); - useEffect( - () => { - if (docId && (!state.persistedDoc || state.persistedDoc.id !== docId)) { - setState((s) => ({ ...s, isLoading: true })); - docStorage - .load(docId) - .then((doc) => { - core.chrome.recentlyAccessed.add(getFullPath(docId), doc.title, docId); - getAllIndexPatterns( - _.uniq( - doc.references.filter(({ type }) => type === 'index-pattern').map(({ id }) => id) - ), - data.indexPatterns, - core.notifications - ) - .then((indexPatterns) => { - // Don't overwrite any pinned filters - data.query.filterManager.setAppFilters( - injectFilterReferences(doc.state.filters, doc.references) - ); - setState((s) => ({ - ...s, - isLoading: false, - persistedDoc: doc, - lastKnownDoc: doc, - query: doc.state.query, - indexPatternsForTopNav: indexPatterns, - })); - }) - .catch((e) => { - setState((s) => ({ ...s, isLoading: false })); - - redirectTo(); - }); + useEffect(() => { + if ( + !initialInput || + (attributeService.inputIsRefType(initialInput) && + initialInput.savedObjectId === state.persistedDoc?.savedObjectId) + ) { + return; + } + + setState((s) => ({ ...s, isLoading: true })); + attributeService + .unwrapAttributes(initialInput) + .then((attributes) => { + if (!initialInput) { + return; + } + const doc = { + ...initialInput, + ...attributes, + type: LENS_EMBEDDABLE_TYPE, + }; + + if (attributeService.inputIsRefType(initialInput)) { + chrome.recentlyAccessed.add( + getFullPath(initialInput.savedObjectId), + attributes.title, + initialInput.savedObjectId + ); + } + getAllIndexPatterns( + _.uniq(doc.references.filter(({ type }) => type === 'index-pattern').map(({ id }) => id)), + data.indexPatterns, + notifications + ) + .then((indexPatterns) => { + // Don't overwrite any pinned filters + data.query.filterManager.setAppFilters( + injectFilterReferences(doc.state.filters, doc.references) + ); + setState((s) => ({ + ...s, + isLoading: false, + persistedDoc: doc, + lastKnownDoc: doc, + query: doc.state.query, + indexPatternsForTopNav: indexPatterns, + })); }) .catch((e) => { setState((s) => ({ ...s, isLoading: false })); - - core.notifications.toasts.addDanger( - i18n.translate('xpack.lens.app.docLoadingError', { - defaultMessage: 'Error loading saved document', - }) - ); - redirectTo(); }); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - core.notifications, - data.indexPatterns, - data.query.filterManager, - docId, - // TODO: These dependencies are changing too often - // docStorage, - // redirectTo, - // state.persistedDoc, - ] - ); + }) + .catch((e) => { + setState((s) => ({ ...s, isLoading: false })); + notifications.toasts.addDanger( + i18n.translate('xpack.lens.app.docLoadingError', { + defaultMessage: 'Error loading saved document', + }) + ); + + redirectTo(); + }); + }, [ + notifications, + data.indexPatterns, + data.query.filterManager, + initialInput, + attributeService, + redirectTo, + chrome.recentlyAccessed, + state.persistedDoc?.savedObjectId, + state.persistedDoc?.state, + ]); const runSave = async ( saveProps: Omit & { returnToOrigin: boolean; onTitleDuplicate?: OnSaveProps['onTitleDuplicate']; newDescription?: string; - } + }, + options: { saveToLibrary: boolean } ) => { if (!lastKnownDoc) { return; } - - const doc = { + const docToSave = { ...getLastKnownDocWithoutPinnedFilters()!, description: saveProps.newDescription, - id: saveProps.newCopyOnSave ? undefined : lastKnownDoc.id, + savedObjectId: saveProps.newCopyOnSave ? undefined : lastKnownDoc.savedObjectId, title: saveProps.newTitle, }; - await checkForDuplicateTitle( - { - ...doc, - copyOnSave: saveProps.newCopyOnSave, - lastSavedTitle: lastKnownDoc?.title, - getEsType: () => 'lens', - getDisplayName: () => - i18n.translate('xpack.lens.app.saveModalType', { - defaultMessage: 'Lens visualization', - }), - }, - saveProps.isTitleDuplicateConfirmed, - saveProps.onTitleDuplicate, - { - savedObjectsClient: core.savedObjects.client, - overlays: core.overlays, + // Required to serialize filters in by value mode until + // https://github.com/elastic/kibana/issues/77588 is fixed + if (getIsByValueMode()) { + docToSave.state.filters.forEach((filter) => { + if (typeof filter.meta.value === 'function') { + delete filter.meta.value; + } + }); + } + + const originalInput = saveProps.newCopyOnSave ? undefined : initialInput; + const originalSavedObjectId = (originalInput as LensByReferenceInput)?.savedObjectId; + if (options.saveToLibrary && !originalInput) { + await checkForDuplicateTitle( + { + ...docToSave, + copyOnSave: saveProps.newCopyOnSave, + lastSavedTitle: lastKnownDoc.title, + getEsType: () => 'lens', + getDisplayName: () => + i18n.translate('xpack.lens.app.saveModalType', { + defaultMessage: 'Lens visualization', + }), + }, + saveProps.isTitleDuplicateConfirmed, + saveProps.onTitleDuplicate, + { + savedObjectsClient, + overlays, + } + ); + } + try { + const newInput = (await attributeService.wrapAttributes( + docToSave, + options.saveToLibrary, + originalInput + )) as LensEmbeddableInput; + + if (saveProps.returnToOrigin && redirectToOrigin) { + // disabling the validation on app leave because the document has been saved. + onAppLeave((actions) => { + return actions.default(); + }); + redirectToOrigin({ input: newInput, isCopied: saveProps.newCopyOnSave }); + return; } - ); - const newlyCreated: boolean = saveProps.newCopyOnSave || !lastKnownDoc?.id; - docStorage - .save(doc) - .then(({ id }) => { - core.chrome.recentlyAccessed.add(getFullPath(id), doc.title, id); - // Prevents unnecessary network request and disables save button - const newDoc = { ...doc, id }; - const currentOriginatingApp = state.originatingApp; + if ( + attributeService.inputIsRefType(newInput) && + newInput.savedObjectId !== originalSavedObjectId + ) { + chrome.recentlyAccessed.add( + getFullPath(newInput.savedObjectId), + docToSave.title, + newInput.savedObjectId + ); setState((s) => ({ ...s, isSaveModalVisible: false, - originatingApp: - newlyCreated && !saveProps.returnToOrigin ? undefined : currentOriginatingApp, - persistedDoc: newDoc, - lastKnownDoc: newDoc, + isLinkedToOriginatingApp: false, })); - if (docId !== id || saveProps.returnToOrigin) { - redirectTo(id, saveProps.returnToOrigin, newlyCreated); - } - }) - .catch((e) => { - // eslint-disable-next-line no-console - console.dir(e); - trackUiEvent('save_failed'); - core.notifications.toasts.addDanger( - i18n.translate('xpack.lens.app.docSavingError', { - defaultMessage: 'Error saving document', - }) - ); - setState((s) => ({ ...s, isSaveModalVisible: false })); - }); - }; + redirectTo(newInput.savedObjectId); + return; + } - const onError = useCallback( - (e: { message: string }) => - core.notifications.toasts.addDanger({ - title: e.message, - }), - [core.notifications.toasts] - ); + const newDoc = { + ...docToSave, + ...newInput, + }; + setState((s) => ({ + ...s, + persistedDoc: newDoc, + lastKnownDoc: newDoc, + isSaveModalVisible: false, + isLinkedToOriginatingApp: false, + })); + } catch (e) { + // eslint-disable-next-line no-console + console.dir(e); + trackUiEvent('save_failed'); + notifications.toasts.addDanger( + i18n.translate('xpack.lens.app.docSavingError', { + defaultMessage: 'Error saving document', + }) + ); + setState((s) => ({ ...s, isSaveModalVisible: false })); + } + }; const { TopNavMenu } = navigation.ui; + const savingPermitted = Boolean(state.isSaveable && application.capabilities.visualize.save); + const topNavConfig = getLensTopNavConfig({ + showSaveAndReturn: Boolean( + state.isLinkedToOriginatingApp && + // Temporarily required until the 'by value' paradigm is default. + (dashboardFeatureFlag.allowByValueEmbeddables || Boolean(initialInput)) + ), + isByValueMode: getIsByValueMode(), + showCancel: Boolean(state.isLinkedToOriginatingApp), + savingPermitted, + actions: { + saveAndReturn: () => { + if (savingPermitted && lastKnownDoc) { + // disabling the validation on app leave because the document has been saved. + onAppLeave((actions) => { + return actions.default(); + }); + runSave( + { + newTitle: lastKnownDoc.title, + newCopyOnSave: false, + isTitleDuplicateConfirmed: false, + returnToOrigin: true, + }, + { + saveToLibrary: + (initialInput && attributeService.inputIsRefType(initialInput)) ?? false, + } + ); + } + }, + showSaveModal: () => { + if (savingPermitted) { + setState((s) => ({ ...s, isSaveModalVisible: true })); + } + }, + cancel: () => { + if (redirectToOrigin) { + redirectToOrigin(); + } + }, + }, + }); + return ( - - -
-
- { - if (savingPermitted) { - runSave({ - newTitle: lastKnownDoc.title, - newCopyOnSave: false, - isTitleDuplicateConfirmed: false, - returnToOrigin: true, - }); - } - }, - testId: 'lnsApp_saveAndReturnButton', - disableButton: !savingPermitted, - }, - ] - : []), - { - label: - lastKnownDoc?.id && !!state.originatingApp - ? i18n.translate('xpack.lens.app.saveAs', { - defaultMessage: 'Save as', - }) - : i18n.translate('xpack.lens.app.save', { - defaultMessage: 'Save', - }), - emphasize: !state.originatingApp || !lastKnownDoc?.id, - run: () => { - if (savingPermitted) { - setState((s) => ({ ...s, isSaveModalVisible: true })); - } - }, - testId: 'lnsApp_saveButton', - disableButton: !savingPermitted, + <> +
+
+ { + const { dateRange, query } = payload; + if ( + dateRange.from !== state.dateRange.fromDate || + dateRange.to !== state.dateRange.toDate + ) { + data.query.timefilter.timefilter.setTime(dateRange); + trackUiEvent('app_date_change'); + } else { + trackUiEvent('app_query_change'); + } + setState((s) => ({ + ...s, + dateRange: { + fromDate: dateRange.from, + toDate: dateRange.to, }, - ]} - data-test-subj="lnsApp_topNav" - screenTitle={'lens'} - onQuerySubmit={(payload) => { - const { dateRange, query } = payload; + query: query || s.query, + })); + }} + onSaved={(savedQuery) => { + setState((s) => ({ ...s, savedQuery })); + }} + onSavedQueryUpdated={(savedQuery) => { + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = data.query.filterManager.getGlobalFilters(); + data.query.filterManager.setFilters([...globalFilters, ...savedQueryFilters]); + setState((s) => ({ + ...s, + savedQuery: { ...savedQuery }, // Shallow query for reference issues + dateRange: savedQuery.attributes.timefilter + ? { + fromDate: savedQuery.attributes.timefilter.from, + toDate: savedQuery.attributes.timefilter.to, + } + : s.dateRange, + })); + }} + onClearSavedQuery={() => { + data.query.filterManager.setFilters(data.query.filterManager.getGlobalFilters()); + setState((s) => ({ + ...s, + savedQuery: undefined, + filters: data.query.filterManager.getGlobalFilters(), + query: data.query.queryString.getDefaultQuery(), + })); + }} + query={state.query} + dateRangeFrom={state.dateRange.fromDate} + dateRangeTo={state.dateRange.toDate} + indicateNoData={state.indicateNoData} + /> +
+ {(!state.isLoading || state.persistedDoc) && ( + { + if (isSaveable !== state.isSaveable) { + setState((s) => ({ ...s, isSaveable })); + } + if (!_.isEqual(state.persistedDoc, doc)) { + setState((s) => ({ ...s, lastKnownDoc: doc })); + } + // Update the cached index patterns if the user made a change to any of them if ( - dateRange.from !== state.dateRange.fromDate || - dateRange.to !== state.dateRange.toDate + state.indexPatternsForTopNav.length !== filterableIndexPatterns.length || + filterableIndexPatterns.some( + (id) => + !state.indexPatternsForTopNav.find((indexPattern) => indexPattern.id === id) + ) ) { - data.query.timefilter.timefilter.setTime(dateRange); - trackUiEvent('app_date_change'); - } else { - trackUiEvent('app_query_change'); + getAllIndexPatterns( + filterableIndexPatterns, + data.indexPatterns, + notifications + ).then((indexPatterns) => { + if (indexPatterns) { + setState((s) => ({ ...s, indexPatternsForTopNav: indexPatterns })); + } + }); } - - setState((s) => ({ - ...s, - dateRange: { - fromDate: dateRange.from, - toDate: dateRange.to, - }, - query: query || s.query, - })); - }} - appName={'lens'} - indexPatterns={state.indexPatternsForTopNav} - showSearchBar={true} - showDatePicker={true} - showQueryBar={true} - showFilterBar={true} - showSaveQuery={core.application.capabilities.visualize.saveQuery as boolean} - savedQuery={state.savedQuery} - onSaved={(savedQuery) => { - setState((s) => ({ ...s, savedQuery })); - }} - onSavedQueryUpdated={(savedQuery) => { - const savedQueryFilters = savedQuery.attributes.filters || []; - const globalFilters = data.query.filterManager.getGlobalFilters(); - data.query.filterManager.setFilters([...globalFilters, ...savedQueryFilters]); - setState((s) => ({ - ...s, - savedQuery: { ...savedQuery }, // Shallow query for reference issues - dateRange: savedQuery.attributes.timefilter - ? { - fromDate: savedQuery.attributes.timefilter.from, - toDate: savedQuery.attributes.timefilter.to, - } - : s.dateRange, - })); - }} - onClearSavedQuery={() => { - data.query.filterManager.setFilters(data.query.filterManager.getGlobalFilters()); - setState((s) => ({ - ...s, - savedQuery: undefined, - filters: data.query.filterManager.getGlobalFilters(), - query: data.query.queryString.getDefaultQuery(), - })); - }} - query={state.query} - dateRangeFrom={state.dateRange.fromDate} - dateRangeTo={state.dateRange.toDate} - indicateNoData={state.indicateNoData} - /> -
- - {(!state.isLoading || state.persistedDoc) && ( - { - if (isSaveable !== state.isSaveable) { - setState((s) => ({ ...s, isSaveable })); - } - if (!_.isEqual(state.persistedDoc, doc)) { - setState((s) => ({ ...s, lastKnownDoc: doc })); - } - - // Update the cached index patterns if the user made a change to any of them - if ( - state.indexPatternsForTopNav.length !== filterableIndexPatterns.length || - filterableIndexPatterns.some( - (id) => - !state.indexPatternsForTopNav.find((indexPattern) => indexPattern.id === id) - ) - ) { - getAllIndexPatterns( - filterableIndexPatterns, - data.indexPatterns, - core.notifications - ).then((indexPatterns) => { - if (indexPatterns) { - setState((s) => ({ ...s, indexPatternsForTopNav: indexPatterns })); - } - }); - } - }, - }} - /> - )} -
- {lastKnownDoc && state.isSaveModalVisible && ( - runSave(props)} - onClose={() => setState((s) => ({ ...s, isSaveModalVisible: false }))} - getAppNameFromId={getAppNameFromId} - documentInfo={{ - id: lastKnownDoc.id, - title: lastKnownDoc.title || '', - description: lastKnownDoc.description || '', + }, }} - objectType={i18n.translate('xpack.lens.app.saveModalType', { - defaultMessage: 'Lens visualization', - })} - data-test-subj="lnsApp_saveModalOrigin" /> )} - - +
+ {lastKnownDoc && state.isSaveModalVisible && ( + runSave(props, { saveToLibrary: true })} + onClose={() => { + setState((s) => ({ ...s, isSaveModalVisible: false })); + }} + getAppNameFromId={() => getOriginatingAppName()} + documentInfo={{ + id: lastKnownDoc.savedObjectId, + title: lastKnownDoc.title || '', + description: lastKnownDoc.description || '', + }} + returnToOriginSwitchLabel={ + getIsByValueMode() && initialInput + ? i18n.translate('xpack.lens.app.updatePanel', { + defaultMessage: 'Update panel on {originatingAppName}', + values: { originatingAppName: getOriginatingAppName() }, + }) + : undefined + } + objectType={i18n.translate('xpack.lens.app.saveModalType', { + defaultMessage: 'Lens visualization', + })} + data-test-subj="lnsApp_saveModalOrigin" + /> + )} + ); } diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx new file mode 100644 index 0000000000000..f6234d063d8cd --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { TopNavMenuData } from '../../../../../src/plugins/navigation/public'; +import { LensTopNavActions } from './types'; + +export function getLensTopNavConfig(options: { + showSaveAndReturn: boolean; + showCancel: boolean; + isByValueMode: boolean; + actions: LensTopNavActions; + savingPermitted: boolean; +}): TopNavMenuData[] { + const { showSaveAndReturn, showCancel, actions, savingPermitted } = options; + const topNavMenu: TopNavMenuData[] = []; + + const saveButtonLabel = options.isByValueMode + ? i18n.translate('xpack.lens.app.addToLibrary', { + defaultMessage: 'Save to library', + }) + : options.showSaveAndReturn + ? i18n.translate('xpack.lens.app.saveAs', { + defaultMessage: 'Save as', + }) + : i18n.translate('xpack.lens.app.save', { + defaultMessage: 'Save', + }); + + if (showSaveAndReturn) { + topNavMenu.push({ + label: i18n.translate('xpack.lens.app.saveAndReturn', { + defaultMessage: 'Save and return', + }), + emphasize: true, + iconType: 'check', + run: actions.saveAndReturn, + testId: 'lnsApp_saveAndReturnButton', + disableButton: !savingPermitted, + description: i18n.translate('xpack.lens.app.saveAndReturnButtonAriaLabel', { + defaultMessage: 'Save the current lens visualization and return to the last app', + }), + }); + } + + topNavMenu.push({ + label: saveButtonLabel, + emphasize: !showSaveAndReturn, + run: actions.showSaveModal, + testId: 'lnsApp_saveButton', + description: i18n.translate('xpack.lens.app.saveButtonAriaLabel', { + defaultMessage: 'Save the current lens visualization', + }), + disableButton: !savingPermitted, + }); + + if (showCancel) { + topNavMenu.push({ + label: i18n.translate('xpack.lens.app.cancel', { + defaultMessage: 'cancel', + }), + run: actions.cancel, + testId: 'lnsApp_cancelButton', + description: i18n.translate('xpack.lens.app.cancelButtonAriaLabel', { + defaultMessage: 'Return to the last app without saving changes', + }), + }); + } + return topNavMenu; +} diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index ebc38e4929f6c..0d50e541d3e48 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -11,6 +11,7 @@ import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom import { render, unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; +import { DashboardFeatureFlagConfig } from 'src/plugins/dashboard/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_telemetry'; @@ -18,76 +19,116 @@ import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_te import { App } from './app'; import { EditorFrameStart } from '../types'; import { addHelpMenuToAppChrome } from '../help_menu_util'; -import { SavedObjectIndexStore } from '../persistence'; import { LensPluginStartDependencies } from '../plugin'; -import { LENS_EMBEDDABLE_TYPE } from '../../common'; +import { LENS_EMBEDDABLE_TYPE, LENS_EDIT_BY_VALUE } from '../../common'; +import { + LensEmbeddableInput, + LensByReferenceInput, + LensByValueInput, +} from '../editor_frame_service/embeddable/embeddable'; +import { LensAttributeService } from '../lens_attribute_service'; +import { LensAppServices, RedirectToOriginProps } from './types'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; export async function mountApp( core: CoreSetup, params: AppMountParameters, - createEditorFrame: EditorFrameStart['createInstance'] + mountProps: { + createEditorFrame: EditorFrameStart['createInstance']; + getByValueFeatureFlag: () => Promise; + attributeService: LensAttributeService; + } ) { + const { createEditorFrame, getByValueFeatureFlag, attributeService } = mountProps; const [coreStart, startDependencies] = await core.getStartServices(); - const { data: dataStart, navigation, embeddable } = startDependencies; - const savedObjectsClient = coreStart.savedObjects.client; - addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks); + const { data, navigation, embeddable } = startDependencies; + + const instance = await createEditorFrame(); + const storage = new Storage(localStorage); + const stateTransfer = embeddable?.getStateTransfer(params.history); + const embeddableEditorIncomingState = stateTransfer?.getIncomingEditorState(); + + const lensServices: LensAppServices = { + data, + storage, + navigation, + attributeService, + http: coreStart.http, + chrome: coreStart.chrome, + overlays: coreStart.overlays, + uiSettings: coreStart.uiSettings, + application: coreStart.application, + notifications: coreStart.notifications, + savedObjectsClient: coreStart.savedObjects.client, + getOriginatingAppName: () => { + return embeddableEditorIncomingState?.originatingApp + ? stateTransfer?.getAppNameFromId(embeddableEditorIncomingState.originatingApp) + : undefined; + }, + // Temporarily required until the 'by value' paradigm is default. + dashboardFeatureFlag: await getByValueFeatureFlag(), + }; + + addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks); coreStart.chrome.docTitle.change( i18n.translate('xpack.lens.pageTitle', { defaultMessage: 'Lens' }) ); - const stateTransfer = embeddable?.getStateTransfer(params.history); - const { originatingApp } = - stateTransfer?.getIncomingEditorState({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; - - const instance = await createEditorFrame(); - setReportManager( new LensReportManager({ - storage: new Storage(localStorage), http: core.http, + storage, }) ); - const redirectTo = ( - routeProps: RouteComponentProps<{ id?: string }>, - id?: string, - returnToOrigin?: boolean, - newlyCreated?: boolean - ) => { - if (!id) { + + const getInitialInput = ( + routeProps: RouteComponentProps<{ id?: string }> + ): LensEmbeddableInput | undefined => { + if (routeProps.match.params.id) { + return { savedObjectId: routeProps.match.params.id } as LensByReferenceInput; + } + if (embeddableEditorIncomingState?.valueInput) { + return embeddableEditorIncomingState?.valueInput as LensByValueInput; + } + }; + + const redirectTo = (routeProps: RouteComponentProps<{ id?: string }>, savedObjectId?: string) => { + if (!savedObjectId) { routeProps.history.push('/'); - } else if (!originatingApp) { - routeProps.history.push(`/edit/${id}`); - } else if (!!originatingApp && id && returnToOrigin) { - routeProps.history.push(`/edit/${id}`); - - if (newlyCreated && stateTransfer) { - stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { - state: { id, type: LENS_EMBEDDABLE_TYPE }, - }); - } else { - coreStart.application.navigateToApp(originatingApp); - } + } else { + routeProps.history.push(`/edit/${savedObjectId}`); } }; + const redirectToOrigin = (props?: RedirectToOriginProps) => { + if (!embeddableEditorIncomingState?.originatingApp) { + throw new Error('redirectToOrigin called without an originating app'); + } + if (stateTransfer && props?.input) { + const { input, isCopied } = props; + stateTransfer.navigateToWithEmbeddablePackage(embeddableEditorIncomingState?.originatingApp, { + state: { + embeddableId: isCopied ? undefined : embeddableEditorIncomingState.embeddableId, + type: LENS_EMBEDDABLE_TYPE, + input, + }, + }); + } else { + coreStart.application.navigateToApp(embeddableEditorIncomingState?.originatingApp); + } + }; + + // const featureFlagConfig = await getByValueFeatureFlag(); const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => { trackUiEvent('loaded'); - return ( - redirectTo(routeProps, id, returnToOrigin, newlyCreated) - } - originatingApp={originatingApp} - getAppNameFromId={stateTransfer.getAppNameFromId} + initialInput={getInitialInput(routeProps)} + redirectTo={(savedObjectId?: string) => redirectTo(routeProps, savedObjectId)} + redirectToOrigin={redirectToOrigin} onAppLeave={params.onAppLeave} setHeaderActionMenu={params.setHeaderActionMenu} history={routeProps.history} @@ -103,13 +144,16 @@ export async function mountApp( params.element.classList.add('lnsAppWrapper'); render( - - - - - - - + + + + + + + + + + , params.element ); diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts new file mode 100644 index 0000000000000..fcdd0b20f8d27 --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { History } from 'history'; +import { + ApplicationStart, + AppMountParameters, + ChromeStart, + HttpStart, + IUiSettingsClient, + NotificationsStart, + OverlayStart, + SavedObjectsStart, +} from '../../../../../src/core/public'; +import { + DataPublicPluginStart, + Filter, + IndexPattern, + Query, + SavedQuery, +} from '../../../../../src/plugins/data/public'; +import { Document } from '../persistence'; +import { LensEmbeddableInput } from '../editor_frame_service/embeddable/embeddable'; +import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public'; +import { LensAttributeService } from '../lens_attribute_service'; +import { IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; +import { DashboardFeatureFlagConfig } from '../../../../../src/plugins/dashboard/public'; +import { EmbeddableEditorState } from '../../../../../src/plugins/embeddable/public'; +import { EditorFrameInstance } from '..'; + +export interface LensAppState { + isLoading: boolean; + persistedDoc?: Document; + lastKnownDoc?: Document; + isSaveModalVisible: boolean; + + // Used to show a popover that guides the user towards changing the date range when no data is available. + indicateNoData: boolean; + + // index patterns used to determine which filters are available in the top nav. + indexPatternsForTopNav: IndexPattern[]; + + // Determines whether the lens editor shows the 'save and return' button, and the originating app breadcrumb. + isLinkedToOriginatingApp?: boolean; + + // Properties needed to interface with TopNav + dateRange: { + fromDate: string; + toDate: string; + }; + query: Query; + filters: Filter[]; + savedQuery?: SavedQuery; + isSaveable: boolean; +} + +export interface RedirectToOriginProps { + input?: LensEmbeddableInput; + isCopied?: boolean; +} + +export interface LensAppProps { + history: History; + editorFrame: EditorFrameInstance; + onAppLeave: AppMountParameters['onAppLeave']; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + redirectTo: (savedObjectId?: string) => void; + redirectToOrigin?: (props?: RedirectToOriginProps) => void; + + // The initial input passed in by the container when editing. Can be either by reference or by value. + initialInput?: LensEmbeddableInput; + + // State passed in by the container which is used to determine the id of the Originating App. + incomingState?: EmbeddableEditorState; +} + +export interface LensAppServices { + http: HttpStart; + chrome: ChromeStart; + overlays: OverlayStart; + storage: IStorageWrapper; + data: DataPublicPluginStart; + uiSettings: IUiSettingsClient; + application: ApplicationStart; + notifications: NotificationsStart; + navigation: NavigationPublicPluginStart; + attributeService: LensAttributeService; + savedObjectsClient: SavedObjectsStart['client']; + getOriginatingAppName: () => string | undefined; + + // Temporarily required until the 'by value' paradigm is default. + dashboardFeatureFlag: DashboardFeatureFlagConfig; +} + +export interface LensTopNavActions { + saveAndReturn: () => void; + showSaveModal: () => void; + cancel: () => void; +} diff --git a/x-pack/plugins/lens/public/assets/drop_illustration.tsx b/x-pack/plugins/lens/public/assets/drop_illustration.tsx new file mode 100644 index 0000000000000..1076f4875d60c --- /dev/null +++ b/x-pack/plugins/lens/public/assets/drop_illustration.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const DropIllustration = ({ title, titleId, ...props }: Omit) => ( + + {title ? {title} : null} + + + + + + + + +); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss index a4d8288d5e600..7f7385f029ed4 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss @@ -6,6 +6,7 @@ margin-bottom: $euiSize; display: flex; flex-direction: column; + position: relative; // For positioning the dnd overlay .lnsWorkspacePanelWrapper__pageContentHeader { @include euiTitle('xs'); @@ -24,8 +25,7 @@ display: flex; align-items: stretch; justify-content: stretch; - overflow: auto; - position: relative; + overflow: hidden; > * { flex: 1 1 100%; @@ -37,6 +37,91 @@ } } +.lnsWorkspacePanel__dragDrop { + // Disable the coloring of the DnD for this element as we'll + // Color the whole panel instead + background-color: transparent !important; // sass-lint:disable-line no-important +} + +.lnsExpressionRenderer { + .lnsDragDrop-isDropTarget & { + transition: filter $euiAnimSpeedNormal ease-in-out, opacity $euiAnimSpeedNormal ease-in-out; + filter: blur($euiSizeXS); + opacity: .25; + } +} + +.lnsWorkspacePanel__emptyContent { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; + display: flex; + justify-content: center; + align-items: center; + transition: background-color $euiAnimSpeedNormal ease-in-out; + + .lnsDragDrop-isDropTarget & { + background-color: transparentize($euiColorSecondary, .9); + + p { + transition: filter $euiAnimSpeedNormal ease-in-out; + filter: blur(5px); + } + } + + .lnsDragDrop-isActiveDropTarget & { + background-color: transparentize($euiColorSecondary, .75); + + .lnsDropIllustration__hand { + animation: pulseArrowContinuous 1.5s ease-in-out 0s infinite normal forwards; + } + } + + &.lnsWorkspacePanel__emptyContent-onTop p { + display: none; + } +} + .lnsWorkspacePanelWrapper__toolbar { margin-bottom: 0; } + +.lnsDropIllustration__adjustFill { + fill: $euiColorFullShade; +} + +.lnsWorkspacePanel__dropIllustration { + overflow: visible; // Shows arrow animation when it gets out of bounds + margin-top: $euiSizeL; + margin-bottom: $euiSizeXXL; + // Drop shadow values is a dupe of @euiBottomShadowMedium but used as a filter + // Hard-coded px values OK (@cchaos) + // sass-lint:disable-block indentation + filter: + drop-shadow(0 6px 12px transparentize($euiShadowColor, .8)) + drop-shadow(0 4px 4px transparentize($euiShadowColor, .8)) + drop-shadow(0 2px 2px transparentize($euiShadowColor, .8)); +} + +.lnsDropIllustration__hand { + animation: pulseArrow 5s ease-in-out 0s infinite normal forwards; +} + +@keyframes pulseArrow { + 0% { transform: translateY(0%); } + 65% { transform: translateY(0%); } + 72% { transform: translateY(10%); } + 79% { transform: translateY(7%); } + 86% { transform: translateY(10%); } + 95% { transform: translateY(0); } +} + +@keyframes pulseArrowContinuous { + 0% { transform: translateY(10%); } + 25% { transform: translateY(15%); } + 50% { transform: translateY(10%); } + 75% { transform: translateY(15%); } + 100% { transform: translateY(10%); } +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/save.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/save.ts index 6da6d5a8c118f..4cb523f128a8c 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/save.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/save.ts @@ -59,7 +59,7 @@ export function getSavedObjectFormat({ return { doc: { - id: state.persistedId, + savedObjectId: state.persistedId, title: state.title, description: state.description, type: 'lens', diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts index c7f505aeca517..80d007e17f711 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts @@ -376,7 +376,7 @@ describe('editor_frame state management', () => { { type: 'VISUALIZATION_LOADED', doc: { - id: 'b', + savedObjectId: 'b', state: { datasourceStates: { a: { foo: 'c' } }, visualization: { bar: 'd' }, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts index 09674ebf2ade2..fc8daaed059dd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts @@ -156,7 +156,7 @@ export const reducer = (state: EditorFrameState, action: Action): EditorFrameSta case 'VISUALIZATION_LOADED': return { ...state, - persistedId: action.doc.id, + persistedId: action.doc.savedObjectId, title: action.doc.title, description: action.doc.description, datasourceStates: Object.entries(action.doc.state.datasourceStates).reduce( 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 06cd858eda210..e56e55fdd5d6c 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 @@ -5,17 +5,10 @@ */ import React, { useState, useEffect, useMemo, useContext, useCallback } from 'react'; +import classNames from 'classnames'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiImage, - EuiText, - EuiButtonEmpty, - EuiLink, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiButtonEmpty, EuiLink } from '@elastic/eui'; import { CoreStart, CoreSetup } from 'kibana/public'; import { ExecutionContextSearch } from 'src/plugins/expressions'; import { @@ -39,6 +32,7 @@ import { UiActionsStart } from '../../../../../../../src/plugins/ui_actions/publ import { VIS_EVENT_TO_TRIGGER } from '../../../../../../../src/plugins/visualizations/public'; import { DataPublicPluginStart } from '../../../../../../../src/plugins/data/public'; import { WorkspacePanelWrapper } from './workspace_panel_wrapper'; +import { DropIllustration } from '../../../assets/drop_illustration'; export interface WorkspacePanelProps { activeVisualizationId: string | null; @@ -78,11 +72,6 @@ export function InnerWorkspacePanel({ ExpressionRenderer: ExpressionRendererComponent, title, }: WorkspacePanelProps) { - const IS_DARK_THEME = core.uiSettings.get('theme:darkMode'); - const emptyStateGraphicURL = IS_DARK_THEME - ? '/plugins/lens/assets/lens_app_graphic_dark_2x.png' - : '/plugins/lens/assets/lens_app_graphic_light_2x.png'; - const dragDropContext = useContext(DragContext); const suggestionForDraggedField = useMemo( @@ -210,41 +199,54 @@ export function InnerWorkspacePanel({ function renderEmptyWorkspace() { return ( -
- -

- -

- -

- -

-

- - - - - -

-
-
+ +

+ + {expression === null ? ( + + ) : ( + + )} + +

+ + {expression === null && ( + <> +

+ +

+

+ + + + + +

+ + )} +
); } @@ -330,12 +332,14 @@ export function InnerWorkspacePanel({ visualizationMap={visualizationMap} > {renderVisualization()} + {Boolean(suggestionForDraggedField) && expression !== null && renderEmptyWorkspace()} ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx index 1e2df28cad7b1..d48f9ed713caf 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx @@ -5,12 +5,29 @@ */ import { Subject } from 'rxjs'; -import { Embeddable } from './embeddable'; +import { + Embeddable, + LensByValueInput, + LensByReferenceInput, + LensSavedObjectAttributes, + LensEmbeddableInput, +} from './embeddable'; import { ReactExpressionRendererProps } from 'src/plugins/expressions/public'; -import { Query, TimeRange, Filter, TimefilterContract } from 'src/plugins/data/public'; +import { + Query, + TimeRange, + Filter, + TimefilterContract, + IndexPatternsContract, +} from 'src/plugins/data/public'; import { Document } from '../../persistence'; import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable'; +import { coreMock, httpServiceMock } from '../../../../../../src/core/public/mocks'; +import { IBasePath } from '../../../../../../src/core/public'; +import { AttributeService } from '../../../../../../src/plugins/dashboard/public'; +import { Ast } from '@kbn/interpreter/common'; +import { LensAttributeService } from '../../lens_attribute_service'; jest.mock('../../../../../../src/plugins/inspector/public/', () => ({ isAvailable: false, @@ -29,61 +46,95 @@ const savedVis: Document = { visualizationType: '', }; +const attributeServiceMockFromSavedVis = (document: Document): LensAttributeService => { + const core = coreMock.createStart(); + const service = new AttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >( + 'lens', + jest.fn(), + core.savedObjects.client, + core.overlays, + core.i18n.Context, + core.notifications.toasts + ); + service.unwrapAttributes = jest.fn((input: LensByValueInput | LensByReferenceInput) => { + return Promise.resolve({ ...document } as LensSavedObjectAttributes); + }); + service.wrapAttributes = jest.fn(); + return service; +}; + describe('embeddable', () => { let mountpoint: HTMLDivElement; let expressionRenderer: jest.Mock; let getTrigger: jest.Mock; let trigger: { exec: jest.Mock }; + let basePath: IBasePath; + let attributeService: AttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >; beforeEach(() => { mountpoint = document.createElement('div'); expressionRenderer = jest.fn((_props) => null); trigger = { exec: jest.fn() }; getTrigger = jest.fn(() => trigger); + attributeService = attributeServiceMockFromSavedVis(savedVis); + const http = httpServiceMock.createSetupContract({ basePath: '/test' }); + basePath = http.basePath; }); afterEach(() => { mountpoint.remove(); }); - it('should render expression with expression renderer', () => { + it('should render expression with expression renderer', async () => { const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123' } + {} as LensEmbeddableInput ); + await embeddable.initializeSavedVis({} as LensEmbeddableInput); embeddable.render(mountpoint); expect(expressionRenderer).toHaveBeenCalledTimes(1); expect(expressionRenderer.mock.calls[0][0]!.expression).toEqual('my | expression'); }); - it('should re-render if new input is pushed', () => { + it('should re-render if new input is pushed', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: false } }]; const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123' } + { id: '123' } as LensEmbeddableInput ); + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); embeddable.render(mountpoint); embeddable.updateInput({ @@ -95,61 +146,74 @@ describe('embeddable', () => { expect(expressionRenderer).toHaveBeenCalledTimes(2); }); - it('should pass context to embeddable', () => { + it('should pass context to embeddable', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: false } }]; + const input = { savedObjectId: '123', timeRange, query, filters } as LensEmbeddableInput; + const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123', timeRange, query, filters } + input ); + await embeddable.initializeSavedVis(input); embeddable.render(mountpoint); - expect(expressionRenderer.mock.calls[0][0].searchContext).toEqual({ - timeRange, - query: [query, savedVis.state.query], - filters, - }); + expect(expressionRenderer.mock.calls[0][0].searchContext).toEqual( + expect.objectContaining({ + timeRange, + query: [query, savedVis.state.query], + filters, + }) + ); }); - it('should merge external context with query and filters of the saved object', () => { + it('should merge external context with query and filters of the saved object', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: 'external filter' }; const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: false } }]; + const newSavedVis = { + ...savedVis, + state: { + ...savedVis.state, + query: { language: 'kquery', query: 'saved filter' }, + filters: [ + { meta: { alias: 'test', negate: false, disabled: false, indexRefName: 'filter-0' } }, + ], + }, + references: [{ type: 'index-pattern', name: 'filter-0', id: 'my-index-pattern-id' }], + }; + attributeService = attributeServiceMockFromSavedVis(newSavedVis); + + const input = { savedObjectId: '123', timeRange, query, filters } as LensEmbeddableInput; + const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis: { - ...savedVis, - state: { - ...savedVis.state, - query: { language: 'kquery', query: 'saved filter' }, - filters: [ - { meta: { alias: 'test', negate: false, disabled: false, indexRefName: 'filter-0' } }, - ], - }, - references: [{ type: 'index-pattern', name: 'filter-0', id: 'my-index-pattern-id' }], - }, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123', timeRange, query, filters } + input ); + await embeddable.initializeSavedVis(input); embeddable.render(mountpoint); expect(expressionRenderer.mock.calls[0][0].searchContext).toEqual({ @@ -163,20 +227,22 @@ describe('embeddable', () => { }); }); - it('should execute trigger on event from expression renderer', () => { + it('should execute trigger on event from expression renderer', async () => { const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123' } + { id: '123' } as LensEmbeddableInput ); + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); embeddable.render(mountpoint); const onEvent = expressionRenderer.mock.calls[0][0].onEvent!; @@ -190,24 +256,31 @@ describe('embeddable', () => { ); }); - it('should not re-render if only change is in disabled filter', () => { + it('should not re-render if only change is in disabled filter', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: true } }]; const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123', timeRange, query, filters } + { id: '123', timeRange, query, filters } as LensEmbeddableInput ); + await embeddable.initializeSavedVis({ + id: '123', + timeRange, + query, + filters, + } as LensEmbeddableInput); embeddable.render(mountpoint); embeddable.updateInput({ @@ -219,7 +292,7 @@ describe('embeddable', () => { expect(expressionRenderer).toHaveBeenCalledTimes(1); }); - it('should re-render on auto refresh fetch observable', () => { + it('should re-render on auto refresh fetch observable', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: true } }]; @@ -230,18 +303,25 @@ describe('embeddable', () => { } as unknown) as TimefilterContract; const embeddable = new Embeddable( - timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123', timeRange, query, filters } + { id: '123', timeRange, query, filters } as LensEmbeddableInput ); + await embeddable.initializeSavedVis({ + id: '123', + timeRange, + query, + filters, + } as LensEmbeddableInput); embeddable.render(mountpoint); autoRefreshFetchSubject.next(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index 4df218a3e94e9..61a5d8cacdc4f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -13,10 +13,12 @@ import { Query, TimefilterContract, TimeRange, + IndexPattern, } from 'src/plugins/data/public'; import { ExecutionContextSearch } from 'src/plugins/expressions'; import { Subscription } from 'rxjs'; +import { Ast } from '@kbn/interpreter/common'; import { ExpressionRendererEvent, ReactExpressionRendererType, @@ -28,41 +30,56 @@ import { EmbeddableInput, EmbeddableOutput, IContainer, + SavedObjectEmbeddableInput, + ReferenceOrValueEmbeddable, } from '../../../../../../src/plugins/embeddable/public'; import { DOC_TYPE, Document, injectFilterReferences } from '../../persistence'; import { ExpressionWrapper } from './expression_wrapper'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { isLensBrushEvent, isLensFilterEvent } from '../../types'; -export interface LensEmbeddableConfiguration { - expression: string | null; - savedVis: Document; - editUrl: string; - editPath: string; - editable: boolean; - indexPatterns?: IIndexPattern[]; -} +import { IndexPatternsContract } from '../../../../../../src/plugins/data/public'; +import { getEditPath } from '../../../common'; +import { IBasePath } from '../../../../../../src/core/public'; +import { LensAttributeService } from '../../lens_attribute_service'; -export interface LensEmbeddableInput extends EmbeddableInput { - timeRange?: TimeRange; - query?: Query; - filters?: Filter[]; -} +export type LensSavedObjectAttributes = Omit; + +export type LensByValueInput = { + attributes: LensSavedObjectAttributes; +} & EmbeddableInput; + +export type LensByReferenceInput = SavedObjectEmbeddableInput & EmbeddableInput; +export type LensEmbeddableInput = LensByValueInput | LensByReferenceInput; export interface LensEmbeddableOutput extends EmbeddableOutput { indexPatterns?: IIndexPattern[]; } -export class Embeddable extends AbstractEmbeddable { +export interface LensEmbeddableDeps { + attributeService: LensAttributeService; + documentToExpression: (doc: Document) => Promise; + toExpressionString: (astObj: Ast, type?: string) => string; + editable: boolean; + indexPatternService: IndexPatternsContract; + expressionRenderer: ReactExpressionRendererType; + timefilter: TimefilterContract; + basePath: IBasePath; + getTrigger?: UiActionsStart['getTrigger'] | undefined; +} + +export class Embeddable + extends AbstractEmbeddable + implements ReferenceOrValueEmbeddable { type = DOC_TYPE; private expressionRenderer: ReactExpressionRendererType; - private getTrigger: UiActionsStart['getTrigger'] | undefined; - private expression: string | null; - private savedVis: Document; + private savedVis: Document | undefined; + private expression: string | undefined | null; private domNode: HTMLElement | Element | undefined; private subscription: Subscription; private autoRefreshFetchSubscription: Subscription; + private isInitialized = false; private externalSearchContext: { timeRange?: TimeRange; @@ -72,50 +89,32 @@ export class Embeddable extends AbstractEmbeddable this.onContainerStateChanged(initialInput)); this.subscription = this.getInput$().subscribe((input) => this.onContainerStateChanged(input)); - this.onContainerStateChanged(initialInput); - this.autoRefreshFetchSubscription = timefilter + this.autoRefreshFetchSubscription = deps.timefilter .getAutoRefreshFetch$() .subscribe(this.reload.bind(this)); } public supportedTriggers() { + if (!this.savedVis) { + return []; + } switch (this.savedVis.visualizationType) { case 'lnsXY': return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush]; @@ -128,6 +127,22 @@ export class Embeddable extends AbstractEmbeddable !filter.meta.disabled) @@ -144,9 +159,7 @@ export class Embeddable extends AbstractEmbeddable, @@ -173,6 +189,9 @@ export class Embeddable extends AbstractEmbeddable { - if (!this.getTrigger || this.input.disableTriggers) { + if (!this.deps.getTrigger || this.input.disableTriggers) { return; } if (isLensBrushEvent(event)) { - this.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ data: event.data, embeddable: this, }); } if (isLensFilterEvent(event)) { - this.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ data: event.data, embeddable: this, }); } }; - destroy() { - super.destroy(); - if (this.domNode) { - unmountComponentAtNode(this.domNode); - } - if (this.subscription) { - this.subscription.unsubscribe(); - } - this.autoRefreshFetchSubscription.unsubscribe(); - } - - reload() { + async reload() { const currentTime = Date.now(); if (this.externalSearchContext.lastReloadRequestTime !== currentTime) { this.externalSearchContext = { @@ -233,4 +241,68 @@ export class Embeddable extends AbstractEmbeddable type === 'index-pattern') + .map(async ({ id }) => { + try { + return await this.deps.indexPatternService.get(id); + } catch (error) { + // Unable to load index pattern, ignore error as the index patterns are only used to + // configure the filter and query bar - there is still a good chance to get the visualization + // to show. + return null; + } + }) + .filter((promise): promise is Promise => Boolean(promise)); + const indexPatterns = await Promise.all(promises); + // passing edit url and index patterns to the output of this embeddable for + // the container to pick them up and use them to configure filter bar and + // config dropdown correctly. + const input = this.getInput(); + const title = input.hidePanelTitles ? '' : input.title || this.savedVis.title; + const savedObjectId = (input as LensByReferenceInput).savedObjectId; + this.updateOutput({ + ...this.getOutput(), + defaultTitle: this.savedVis.title, + title, + editPath: getEditPath(savedObjectId), + editUrl: this.deps.basePath.prepend(`/app/lens${getEditPath(savedObjectId)}`), + indexPatterns, + }); + } + + public inputIsRefType = ( + input: LensByValueInput | LensByReferenceInput + ): input is LensByReferenceInput => { + return this.deps.attributeService.inputIsRefType(input); + }; + + public getInputAsRefType = async (): Promise => { + const input = this.deps.attributeService.getExplicitInputFromEmbeddable(this); + return this.deps.attributeService.getInputAsRefType(input, { + showSaveModal: true, + saveModalTitle: this.getTitle(), + }); + }; + + public getInputAsValueType = async (): Promise => { + const input = this.deps.attributeService.getExplicitInputFromEmbeddable(this); + return this.deps.attributeService.getInputAsValueType(input); + }; + + destroy() { + super.destroy(); + if (this.domNode) { + unmountComponentAtNode(this.domNode); + } + if (this.subscription) { + this.subscription.unsubscribe(); + } + this.autoRefreshFetchSubscription.unsubscribe(); + } } diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts index b8f9f8de1d286..8771d1ebaddb1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts @@ -4,33 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Capabilities, HttpSetup, SavedObjectsClientContract } from 'kibana/public'; +import { Capabilities, HttpSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { RecursiveReadonly } from '@kbn/utility-types'; import { toExpression, Ast } from '@kbn/interpreter/target/common'; import { IndexPatternsContract, - IndexPattern, TimefilterContract, } from '../../../../../../src/plugins/data/public'; import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; import { EmbeddableFactoryDefinition, - ErrorEmbeddable, - EmbeddableInput, IContainer, } from '../../../../../../src/plugins/embeddable/public'; -import { Embeddable } from './embeddable'; -import { SavedObjectIndexStore, DOC_TYPE } from '../../persistence'; -import { getEditPath } from '../../../common'; +import { Embeddable, LensByReferenceInput, LensEmbeddableInput } from './embeddable'; +import { DOC_TYPE } from '../../persistence'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { Document } from '../../persistence/saved_object_store'; +import { LensAttributeService } from '../../lens_attribute_service'; -interface StartServices { +export interface LensEmbeddableStartServices { timefilter: TimefilterContract; coreHttp: HttpSetup; + attributeService: LensAttributeService; capabilities: RecursiveReadonly; - savedObjectsClient: SavedObjectsClientContract; expressionRenderer: ReactExpressionRendererType; indexPatternService: IndexPatternsContract; uiActions?: UiActionsStart; @@ -47,7 +44,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { getIconForSavedObject: () => 'lensApp', }; - constructor(private getStartServices: () => Promise) {} + constructor(private getStartServices: () => Promise) {} public isEditable = async () => { const { capabilities } = await this.getStartServices(); @@ -66,59 +63,40 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { createFromSavedObject = async ( savedObjectId: string, - input: Partial & { id: string }, + input: LensEmbeddableInput, parent?: IContainer ) => { + if (!(input as LensByReferenceInput).savedObjectId) { + (input as LensByReferenceInput).savedObjectId = savedObjectId; + } + return this.create(input, parent); + }; + + async create(input: LensEmbeddableInput, parent?: IContainer) { const { - savedObjectsClient, - coreHttp, - indexPatternService, timefilter, expressionRenderer, documentToExpression, uiActions, + coreHttp, + attributeService, + indexPatternService, } = await this.getStartServices(); - const store = new SavedObjectIndexStore(savedObjectsClient); - const savedVis = await store.load(savedObjectId); - - const promises = savedVis.references - .filter(({ type }) => type === 'index-pattern') - .map(async ({ id }) => { - try { - return await indexPatternService.get(id); - } catch (error) { - // Unable to load index pattern, ignore error as the index patterns are only used to - // configure the filter and query bar - there is still a good chance to get the visualization - // to show. - return null; - } - }); - const indexPatterns = ( - await Promise.all(promises) - ).filter((indexPattern: IndexPattern | null): indexPattern is IndexPattern => - Boolean(indexPattern) - ); - - const expression = await documentToExpression(savedVis); return new Embeddable( - timefilter, - expressionRenderer, - uiActions?.getTrigger, { - savedVis, - editPath: getEditPath(savedObjectId), - editUrl: coreHttp.basePath.prepend(`/app/lens${getEditPath(savedObjectId)}`), + attributeService, + indexPatternService, + timefilter, + expressionRenderer, editable: await this.isEditable(), - indexPatterns, - expression: expression ? toExpression(expression) : null, + basePath: coreHttp.basePath, + getTrigger: uiActions?.getTrigger, + documentToExpression, + toExpressionString: toExpression, }, input, parent ); - }; - - async create(input: EmbeddableInput) { - return new ErrorEmbeddable('Lens can only be created from a saved object', input); } } diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx index 7b1d091c1c8fe..c1b6d74bb49c0 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx @@ -41,7 +41,8 @@ describe('editor_frame service', () => { (async () => { pluginInstance.setup( coreMock.createSetup() as CoreSetup, - pluginSetupDependencies + pluginSetupDependencies, + jest.fn() ); const publicAPI = pluginInstance.start(coreMock.createStart(), pluginStartDependencies); const instance = await publicAPI.createInstance(); @@ -61,7 +62,8 @@ describe('editor_frame service', () => { it('should not have child nodes after unmount', async () => { pluginInstance.setup( coreMock.createSetup() as CoreSetup, - pluginSetupDependencies + pluginSetupDependencies, + jest.fn() ); const publicAPI = pluginInstance.start(coreMock.createStart(), pluginStartDependencies); const instance = await publicAPI.createInstance(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 5fc347179a032..bebc3e6989902 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -25,10 +25,12 @@ import { Document } from '../persistence/saved_object_store'; import { EditorFrame } from './editor_frame'; import { mergeTables } from './merge_tables'; import { formatColumn } from './format_column'; -import { EmbeddableFactory } from './embeddable/embeddable_factory'; +import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory'; import { getActiveDatasourceIdFromDoc } from './editor_frame/state_management'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; +import { DashboardStart } from '../../../../../src/plugins/dashboard/public'; import { persistedStateToExpression } from './editor_frame/state_helpers'; +import { LensAttributeService } from '../lens_attribute_service'; export interface EditorFrameSetupPlugins { data: DataPublicPluginSetup; @@ -39,6 +41,7 @@ export interface EditorFrameSetupPlugins { export interface EditorFrameStartPlugins { data: DataPublicPluginStart; embeddable?: EmbeddableStart; + dashboard?: DashboardStart; expressions: ExpressionsStart; uiActions?: UiActionsStart; } @@ -78,16 +81,17 @@ export class EditorFrameService { public setup( core: CoreSetup, - plugins: EditorFrameSetupPlugins + plugins: EditorFrameSetupPlugins, + getAttributeService: () => LensAttributeService ): EditorFrameSetup { plugins.expressions.registerFunction(() => mergeTables); plugins.expressions.registerFunction(() => formatColumn); - const getStartServices = async () => { + const getStartServices = async (): Promise => { const [coreStart, deps] = await core.getStartServices(); return { + attributeService: getAttributeService(), capabilities: coreStart.application.capabilities, - savedObjectsClient: coreStart.savedObjects.client, coreHttp: coreStart.http, timefilter: deps.data.query.timefilter.timefilter, expressionRenderer: deps.expressions.ReactExpressionRenderer, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.scss b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.scss index 70fb57ee79ee5..155b954e9cf17 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.scss +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.scss @@ -10,27 +10,6 @@ margin-bottom: $euiSizeS; } -/** - * 1. Don't cut off the shadow of the field items - */ - -.lnsInnerIndexPatternDataPanel__listWrapper { - @include euiOverflowShadow; - @include euiScrollBar; - margin-left: -$euiSize; /* 1 */ - position: relative; - flex-grow: 1; - overflow: auto; -} - -.lnsInnerIndexPatternDataPanel__list { - padding-top: $euiSizeS; - position: absolute; - top: 0; - left: $euiSize; /* 1 */ - right: $euiSizeXS; /* 1 */ -} - .lnsInnerIndexPatternDataPanel__fieldItems { // Quick fix for making sure the shadow and focus rings are visible outside the accordion bounds padding: $euiSizeXS $euiSizeXS 0; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index f17bf172b0fb1..7fb64d1613d32 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -623,11 +623,40 @@ describe('IndexPattern Data Panel', () => { ).toEqual(['client', 'source', 'timestampLabel']); }); + it('should show meta fields accordion', async () => { + const wrapper = mountWithIntl( + + ); + wrapper + .find('[data-test-subj="lnsIndexPatternMetaFields"]') + .find('button') + .first() + .simulate('click'); + expect( + wrapper + .find('[data-test-subj="lnsIndexPatternMetaFields"]') + .find(FieldItem) + .first() + .prop('field').name + ).toEqual('_id'); + }); + it('should display NoFieldsCallout when all fields are empty', async () => { const wrapper = mountWithIntl( ); - expect(wrapper.find(NoFieldsCallout).length).toEqual(1); + expect(wrapper.find(NoFieldsCallout).length).toEqual(2); expect( wrapper .find('[data-test-subj="lnsIndexPatternAvailableFields"]') @@ -654,7 +683,7 @@ describe('IndexPattern Data Panel', () => { .length ).toEqual(1); wrapper.setProps({ existingFields: { idx1: {} } }); - expect(wrapper.find(NoFieldsCallout).length).toEqual(1); + expect(wrapper.find(NoFieldsCallout).length).toEqual(2); }); it('should filter down by name', () => { @@ -699,7 +728,7 @@ describe('IndexPattern Data Panel', () => { expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([ 'Records', ]); - expect(wrapper.find(NoFieldsCallout).length).toEqual(2); + expect(wrapper.find(NoFieldsCallout).length).toEqual(3); }); it('should toggle type if clicked again', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index f7adf91e307da..4e85cb5b5d46c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -5,14 +5,13 @@ */ import './datapanel.scss'; -import { uniq, keyBy, groupBy, throttle } from 'lodash'; -import React, { useState, useEffect, memo, useCallback, useMemo } from 'react'; +import { uniq, keyBy, groupBy } from 'lodash'; +import React, { useState, memo, useCallback, useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiContextMenuPanel, EuiContextMenuItem, - EuiContextMenuPanelProps, EuiPopover, EuiCallOut, EuiFormControlLayout, @@ -25,8 +24,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { DataPublicPluginStart, EsQueryConfig, Query, Filter } from 'src/plugins/data/public'; import { DatasourceDataPanelProps, DataType, StateSetter } from '../types'; import { ChildDragDropProvider, DragContextState } from '../drag_drop'; -import { FieldItem } from './field_item'; -import { NoFieldsCallout } from './no_fields_callout'; import { IndexPattern, IndexPatternPrivateState, @@ -37,7 +34,6 @@ import { trackUiEvent } from '../lens_ui_telemetry'; import { syncExistingFields } from './loader'; import { fieldExists } from './pure_helpers'; import { Loader } from '../loader'; -import { FieldsAccordion } from './fields_accordion'; import { esQuery, IIndexPattern } from '../../../../../src/plugins/data/public'; export type Props = DatasourceDataPanelProps & { @@ -52,18 +48,13 @@ export type Props = DatasourceDataPanelProps & { import { LensFieldIcon } from './lens_field_icon'; import { ChangeIndexPattern } from './change_indexpattern'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; - -// TODO the typings for EuiContextMenuPanel are incorrect - watchedItemProps is missing. This can be removed when the types are adjusted -const FixedEuiContextMenuPanel = (EuiContextMenuPanel as unknown) as React.FunctionComponent< - EuiContextMenuPanelProps & { watchedItemProps: string[] } ->; +import { FieldGroups, FieldList } from './field_list'; function sortFields(fieldA: IndexPatternField, fieldB: IndexPatternField) { return fieldA.displayName.localeCompare(fieldB.displayName, undefined, { sensitivity: 'base' }); } const supportedFieldTypes = new Set(['string', 'number', 'boolean', 'date', 'ip', 'document']); -const PAGINATION_SIZE = 50; const fieldTypeNames: Record = { document: i18n.translate('xpack.lens.datatypes.record', { defaultMessage: 'record' }), @@ -212,18 +203,19 @@ interface DataPanelState { isTypeFilterOpen: boolean; isAvailableAccordionOpen: boolean; isEmptyAccordionOpen: boolean; + isMetaAccordionOpen: boolean; } -export interface FieldsGroup { +const defaultFieldGroups: { specialFields: IndexPatternField[]; availableFields: IndexPatternField[]; emptyFields: IndexPatternField[]; -} - -const defaultFieldGroups = { + metaFields: IndexPatternField[]; +} = { specialFields: [], availableFields: [], emptyFields: [], + metaFields: [], }; const fieldFiltersLabel = i18n.translate('xpack.lens.indexPatterns.fieldFiltersLabel', { @@ -261,9 +253,8 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ isTypeFilterOpen: false, isAvailableAccordionOpen: true, isEmptyAccordionOpen: false, + isMetaAccordionOpen: false, }); - const [pageSize, setPageSize] = useState(PAGINATION_SIZE); - const [scrollContainer, setScrollContainer] = useState(undefined); const currentIndexPattern = indexPatterns[currentIndexPatternId]; const allFields = currentIndexPattern.fields; const clearLocalState = () => setLocalState((s) => ({ ...s, nameFilter: '', typeFilter: [] })); @@ -272,17 +263,11 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ (type) => type in fieldTypeNames ); - useEffect(() => { - // Reset the scroll if we have made material changes to the field list - if (scrollContainer) { - scrollContainer.scrollTop = 0; - setPageSize(PAGINATION_SIZE); - } - }, [localState.nameFilter, localState.typeFilter, currentIndexPatternId, scrollContainer]); + const fieldInfoUnavailable = existenceFetchFailed || currentIndexPattern.hasRestrictions; - const fieldGroups: FieldsGroup = useMemo(() => { + const unfilteredFieldGroups: FieldGroups = useMemo(() => { + const fieldByName = keyBy(allFields, 'name'); const containsData = (field: IndexPatternField) => { - const fieldByName = keyBy(allFields, 'name'); const overallField = fieldByName[field.name]; return ( @@ -294,32 +279,105 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ supportedFieldTypes.has(field.type) ); const sorted = allSupportedTypesFields.sort(sortFields); + let groupedFields; // optimization before existingFields are synced if (!hasSyncedExistingFields) { - return { + groupedFields = { ...defaultFieldGroups, ...groupBy(sorted, (field) => { if (field.type === 'document') { return 'specialFields'; + } else if (field.meta) { + return 'metaFields'; } else { return 'emptyFields'; } }), }; } - return { + groupedFields = { ...defaultFieldGroups, ...groupBy(sorted, (field) => { if (field.type === 'document') { return 'specialFields'; + } else if (field.meta) { + return 'metaFields'; } else if (containsData(field)) { return 'availableFields'; } else return 'emptyFields'; }), }; - }, [allFields, existingFields, currentIndexPattern, hasSyncedExistingFields]); - const filteredFieldGroups: FieldsGroup = useMemo(() => { + const fieldGroupDefinitions: FieldGroups = { + SpecialFields: { + fields: groupedFields.specialFields, + fieldCount: 1, + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: false, + isInitiallyOpen: false, + showInAccordion: false, + title: '', + hideDetails: true, + }, + AvailableFields: { + fields: groupedFields.availableFields, + fieldCount: groupedFields.availableFields.length, + isInitiallyOpen: true, + showInAccordion: true, + title: fieldInfoUnavailable + ? i18n.translate('xpack.lens.indexPattern.allFieldsLabel', { + defaultMessage: 'All fields', + }) + : i18n.translate('xpack.lens.indexPattern.availableFieldsLabel', { + defaultMessage: 'Available fields', + }), + + isAffectedByGlobalFilter: !!filters.length, + isAffectedByTimeFilter: true, + hideDetails: fieldInfoUnavailable, + }, + EmptyFields: { + fields: groupedFields.emptyFields, + fieldCount: groupedFields.emptyFields.length, + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: false, + isInitiallyOpen: false, + showInAccordion: true, + hideDetails: false, + title: i18n.translate('xpack.lens.indexPattern.emptyFieldsLabel', { + defaultMessage: 'Empty fields', + }), + }, + MetaFields: { + fields: groupedFields.metaFields, + fieldCount: groupedFields.metaFields.length, + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: false, + isInitiallyOpen: false, + showInAccordion: true, + hideDetails: false, + title: i18n.translate('xpack.lens.indexPattern.metaFieldsLabel', { + defaultMessage: 'Meta fields', + }), + }, + }; + + // do not show empty field accordion if there is no existence information + if (fieldInfoUnavailable) { + delete fieldGroupDefinitions.EmptyFields; + } + + return fieldGroupDefinitions; + }, [ + allFields, + existingFields, + currentIndexPattern, + hasSyncedExistingFields, + fieldInfoUnavailable, + filters.length, + ]); + + const fieldGroups: FieldGroups = useMemo(() => { const filterFieldGroup = (fieldGroup: IndexPatternField[]) => fieldGroup.filter((field) => { if ( @@ -329,76 +387,18 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ ) { return false; } - if (localState.typeFilter.length > 0) { return localState.typeFilter.includes(field.type as DataType); } return true; }); - - return Object.entries(fieldGroups).reduce((acc, [name, fields]) => { - return { - ...acc, - [name]: filterFieldGroup(fields), - }; - }, defaultFieldGroups); - }, [fieldGroups, localState.nameFilter, localState.typeFilter]); - - const lazyScroll = useCallback(() => { - if (scrollContainer) { - const nearBottom = - scrollContainer.scrollTop + scrollContainer.clientHeight > - scrollContainer.scrollHeight * 0.9; - if (nearBottom) { - const displayedFieldsLength = - (localState.isAvailableAccordionOpen ? filteredFieldGroups.availableFields.length : 0) + - (localState.isEmptyAccordionOpen ? filteredFieldGroups.emptyFields.length : 0); - setPageSize( - Math.max( - PAGINATION_SIZE, - Math.min(pageSize + PAGINATION_SIZE * 0.5, displayedFieldsLength) - ) - ); - } - } - }, [ - scrollContainer, - localState.isAvailableAccordionOpen, - localState.isEmptyAccordionOpen, - filteredFieldGroups, - pageSize, - setPageSize, - ]); - - const [paginatedAvailableFields, paginatedEmptyFields]: [ - IndexPatternField[], - IndexPatternField[] - ] = useMemo(() => { - const { availableFields, emptyFields } = filteredFieldGroups; - const isAvailableAccordionOpen = localState.isAvailableAccordionOpen; - const isEmptyAccordionOpen = localState.isEmptyAccordionOpen; - - if (isAvailableAccordionOpen && isEmptyAccordionOpen) { - if (availableFields.length > pageSize) { - return [availableFields.slice(0, pageSize), []]; - } else { - return [availableFields, emptyFields.slice(0, pageSize - availableFields.length)]; - } - } - if (isAvailableAccordionOpen && !isEmptyAccordionOpen) { - return [availableFields.slice(0, pageSize), []]; - } - - if (!isAvailableAccordionOpen && isEmptyAccordionOpen) { - return [[], emptyFields.slice(0, pageSize)]; - } - return [[], []]; - }, [ - localState.isAvailableAccordionOpen, - localState.isEmptyAccordionOpen, - filteredFieldGroups, - pageSize, - ]); + return Object.fromEntries( + Object.entries(unfilteredFieldGroups).map(([name, group]) => [ + name, + { ...group, fields: filterFieldGroup(group.fields) }, + ]) + ); + }, [unfilteredFieldGroups, localState.nameFilter, localState.typeFilter]); const fieldProps = useMemo( () => ({ @@ -423,8 +423,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ ] ); - const fieldInfoUnavailable = existenceFetchFailed || currentIndexPattern.hasRestrictions; - return ( } > - ( @@ -545,115 +543,21 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
-
{ - if (el && !el.dataset.dynamicScroll) { - el.dataset.dynamicScroll = 'true'; - setScrollContainer(el); - } + + field.type === 'document' || + fieldExists(existingFields, currentIndexPattern.title, field.name) + } + fieldProps={fieldProps} + fieldGroups={fieldGroups} + hasSyncedExistingFields={!!hasSyncedExistingFields} + filter={{ + nameFilter: localState.nameFilter, + typeFilter: localState.typeFilter, }} - onScroll={throttle(lazyScroll, 100)} - > -
- {filteredFieldGroups.specialFields.map((field: IndexPatternField) => ( - - ))} - - - { - setLocalState((s) => ({ - ...s, - isAvailableAccordionOpen: open, - })); - const displayedFieldLength = - (open ? filteredFieldGroups.availableFields.length : 0) + - (localState.isEmptyAccordionOpen ? filteredFieldGroups.emptyFields.length : 0); - setPageSize( - Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) - ); - }} - showExistenceFetchError={existenceFetchFailed} - renderCallout={ - - } - /> - - {!fieldInfoUnavailable && ( - { - setLocalState((s) => ({ - ...s, - isEmptyAccordionOpen: open, - })); - const displayedFieldLength = - (localState.isAvailableAccordionOpen - ? filteredFieldGroups.availableFields.length - : 0) + (open ? filteredFieldGroups.emptyFields.length : 0); - setPageSize( - Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) - ); - }} - renderCallout={ - - } - /> - )} - -
-
+ currentIndexPatternId={currentIndexPatternId} + existenceFetchFailed={existenceFetchFailed} + />
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx index 325f18ee9833a..3d692b1f7f5a8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx @@ -75,6 +75,10 @@ export function BucketNestingEditor({ defaultMessage: 'Top values for each {field}', values: { field: fieldName }, }), + range: i18n.translate('xpack.lens.indexPattern.groupingOverallRanges', { + defaultMessage: 'Top values for each {field}', + values: { field: fieldName }, + }), }; const bottomLevelCopy: Record = { @@ -90,6 +94,10 @@ export function BucketNestingEditor({ defaultMessage: 'Overall top {target}', values: { target: target.fieldName }, }), + range: i18n.translate('xpack.lens.indexPattern.groupingSecondRanges', { + defaultMessage: 'Overall top {target}', + values: { target: target.fieldName }, + }), }; return ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 153757ac37da1..2f64a36e0462e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -332,7 +332,6 @@ export function DimensionEditor(props: DimensionEditorProps) { {!incompatibleSelectedOperationType && ParamEditor && ( <> - fieldMap[field].meta); + const [availableFields, emptyFields] = _.partition(nonMetaFields, containsData); const constructFieldsOptions = (fieldsArr: string[], label: string) => fieldsArr.length > 0 && { @@ -138,10 +139,18 @@ export function FieldSelect({ }) ); + const metaFieldsOptions = constructFieldsOptions( + metaFields, + i18n.translate('xpack.lens.indexPattern.metaFieldsLabel', { + defaultMessage: 'Meta fields', + }) + ); + return [ ...fieldNamesToOptions(specialFields), availableFieldsOptions, emptyFieldsOptions, + metaFieldsOptions, ].filter(Boolean); }, [ incompatibleSelectedOperationType, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 1f6d7911b3a33..f141d3f8ecb9e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -184,7 +184,8 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { defaultMessage: 'Click for a field preview, or drag and drop to visualize.', }) : i18n.translate('xpack.lens.indexPattern.fieldStatsButtonEmptyLabel', { - defaultMessage: "This field doesn't have data. Drag and drop to visualize.", + defaultMessage: + 'This field doesn’t have any data but you can still drag and drop to visualize.', }) } type="iInCircle" @@ -195,7 +196,6 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { return ( ('.application') || undefined} @@ -307,7 +307,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { {i18n.translate('xpack.lens.indexPattern.fieldStatsNoData', { defaultMessage: - 'This field is empty because it doesn’t exist in the 500 sampled documents.', + 'This field is empty because it doesn’t exist in the 500 sampled documents. Adding this field to the configuration may result in a blank chart.', })} ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.scss b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.scss new file mode 100644 index 0000000000000..f28581b835b07 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.scss @@ -0,0 +1,20 @@ +/** + * 1. Don't cut off the shadow of the field items + */ + +.lnsIndexPatternFieldList { + @include euiOverflowShadow; + @include euiScrollBar; + margin-left: -$euiSize; /* 1 */ + position: relative; + flex-grow: 1; + overflow: auto; +} + +.lnsIndexPatternFieldList__accordionContainer { + padding-top: $euiSizeS; + position: absolute; + top: 0; + left: $euiSize; /* 1 */ + right: $euiSizeXS; /* 1 */ +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx new file mode 100644 index 0000000000000..4a9b3a0c63e3f --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import './field_list.scss'; +import { throttle } from 'lodash'; +import React, { useState, Fragment, useCallback, useMemo, useEffect } from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { FieldItem } from './field_item'; +import { NoFieldsCallout } from './no_fields_callout'; +import { IndexPatternField } from './types'; +import { FieldItemSharedProps, FieldsAccordion } from './fields_accordion'; +const PAGINATION_SIZE = 50; + +export interface FieldsGroup { + specialFields: IndexPatternField[]; + availableFields: IndexPatternField[]; + emptyFields: IndexPatternField[]; + metaFields: IndexPatternField[]; +} + +export type FieldGroups = Record< + string, + { + fields: IndexPatternField[]; + fieldCount: number; + showInAccordion: boolean; + isInitiallyOpen: boolean; + title: string; + isAffectedByGlobalFilter: boolean; + isAffectedByTimeFilter: boolean; + hideDetails?: boolean; + } +>; + +function getDisplayedFieldsLength( + fieldGroups: FieldGroups, + accordionState: Partial> +) { + return Object.entries(fieldGroups) + .filter(([key]) => accordionState[key]) + .reduce((allFieldCount, [, { fields }]) => allFieldCount + fields.length, 0); +} + +export function FieldList({ + exists, + fieldGroups, + existenceFetchFailed, + fieldProps, + hasSyncedExistingFields, + filter, + currentIndexPatternId, +}: { + exists: (field: IndexPatternField) => boolean; + fieldGroups: FieldGroups; + fieldProps: FieldItemSharedProps; + hasSyncedExistingFields: boolean; + existenceFetchFailed?: boolean; + filter: { + nameFilter: string; + typeFilter: string[]; + }; + currentIndexPatternId: string; +}) { + const [pageSize, setPageSize] = useState(PAGINATION_SIZE); + const [scrollContainer, setScrollContainer] = useState(undefined); + const [accordionState, setAccordionState] = useState>>(() => + Object.fromEntries( + Object.entries(fieldGroups) + .filter(([, { showInAccordion }]) => showInAccordion) + .map(([key, { isInitiallyOpen }]) => [key, isInitiallyOpen]) + ) + ); + + const isAffectedByFieldFilter = !!(filter.typeFilter.length || filter.nameFilter.length); + + useEffect(() => { + // Reset the scroll if we have made material changes to the field list + if (scrollContainer) { + scrollContainer.scrollTop = 0; + setPageSize(PAGINATION_SIZE); + } + }, [filter.nameFilter, filter.typeFilter, currentIndexPatternId, scrollContainer]); + + const lazyScroll = useCallback(() => { + if (scrollContainer) { + const nearBottom = + scrollContainer.scrollTop + scrollContainer.clientHeight > + scrollContainer.scrollHeight * 0.9; + if (nearBottom) { + setPageSize( + Math.max( + PAGINATION_SIZE, + Math.min( + pageSize + PAGINATION_SIZE * 0.5, + getDisplayedFieldsLength(fieldGroups, accordionState) + ) + ) + ); + } + } + }, [scrollContainer, pageSize, setPageSize, fieldGroups, accordionState]); + + const paginatedFields = useMemo(() => { + let remainingItems = pageSize; + return Object.fromEntries( + Object.entries(fieldGroups) + .filter(([, { showInAccordion }]) => showInAccordion) + .map(([key, fieldGroup]) => { + if (!accordionState[key] || remainingItems <= 0) { + return [key, []]; + } + const slicedFieldList = fieldGroup.fields.slice(0, remainingItems); + remainingItems = remainingItems - slicedFieldList.length; + return [key, slicedFieldList]; + }) + ); + }, [pageSize, fieldGroups, accordionState]); + + return ( +
{ + if (el && !el.dataset.dynamicScroll) { + el.dataset.dynamicScroll = 'true'; + setScrollContainer(el); + } + }} + onScroll={throttle(lazyScroll, 100)} + > +
+ {Object.entries(fieldGroups) + .filter(([, { showInAccordion }]) => !showInAccordion) + .flatMap(([, { fields }]) => + fields.map((field) => ( + + )) + )} + + {Object.entries(fieldGroups) + .filter(([, { showInAccordion }]) => showInAccordion) + .map(([key, fieldGroup]) => ( + + { + setAccordionState((s) => ({ + ...s, + [key]: open, + })); + const displayedFieldLength = getDisplayedFieldsLength(fieldGroups, { + ...accordionState, + [key]: open, + }); + setPageSize( + Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) + ); + }} + showExistenceFetchError={existenceFetchFailed} + renderCallout={ + + } + /> + + + ))} +
+
+ ); +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx index b0604efff7b89..7d1c80e5a7f6a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx @@ -71,11 +71,19 @@ describe('Fields Accordion', () => { paginatedFields: indexPattern.fields, fieldProps, renderCallout:
Callout
, - exists: true, + exists: () => true, }; }); it('renders correct number of Field Items', () => { + const wrapper = mountWithIntl( + field.name === 'timestamp'} /> + ); + expect(wrapper.find(FieldItem).at(0).prop('exists')).toEqual(true); + expect(wrapper.find(FieldItem).at(1).prop('exists')).toEqual(false); + }); + + it('passed correct exists flag to each field', () => { const wrapper = mountWithIntl(); expect(wrapper.find(FieldItem).length).toEqual(2); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx index 30a92c21ff661..e531eb72f94ca 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx @@ -45,7 +45,7 @@ export interface FieldsAccordionProps { paginatedFields: IndexPatternField[]; fieldProps: FieldItemSharedProps; renderCallout: JSX.Element; - exists: boolean; + exists: (field: IndexPatternField) => boolean; showExistenceFetchError?: boolean; hideDetails?: boolean; } @@ -71,7 +71,7 @@ export const InnerFieldsAccordion = function InnerFieldsAccordion({ {...fieldProps} key={field.name} field={field} - exists={exists} + exists={exists(field)} hideDetails={hideDetails} /> ), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts index 19213d4afc9bc..ef6abbec9a34d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -197,7 +197,7 @@ function mockClient() { function mockIndexPatternsService() { return ({ get: jest.fn(async (id: '1' | '2') => { - return sampleIndexPatternsFromService[id]; + return { ...sampleIndexPatternsFromService[id], metaFields: [] }; }), } as unknown) as Pick; } @@ -248,6 +248,7 @@ describe('loader', () => { get: jest.fn(async () => ({ id: 'foo', title: 'Foo index', + metaFields: [], typeMeta: { aggs: { date_histogram: { @@ -295,6 +296,55 @@ describe('loader', () => { date_histogram: { agg: 'date_histogram', fixed_interval: 'm' }, }); }); + + it('should map meta flag', async () => { + const cache = await loadIndexPatterns({ + cache: {}, + patterns: ['foo'], + indexPatternsService: ({ + get: jest.fn(async () => ({ + id: 'foo', + title: 'Foo index', + metaFields: ['timestamp'], + typeMeta: { + aggs: { + date_histogram: { + timestamp: { + agg: 'date_histogram', + fixed_interval: 'm', + }, + }, + sum: { + bytes: { + agg: 'sum', + }, + }, + }, + }, + fields: [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ], + })), + } as unknown) as Pick, + }); + + expect(cache.foo.fields.find((f: IndexPatternField) => f.name === 'timestamp')!.meta).toEqual( + true + ); + }); }); describe('loadInitialState', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index 0ab658b961336..c4b1eb9e0c4c4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -63,6 +63,7 @@ export async function loadIndexPatterns({ type: field.type, aggregatable: field.aggregatable, searchable: field.searchable, + meta: indexPattern.metaFields.includes(field.name), esTypes: field.esTypes, scripted: field.scripted, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index 83ae8e47b39ca..7784024b03132 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiForm, EuiFormRow, EuiSwitch, EuiSwitchEvent, @@ -42,7 +41,7 @@ export const dateHistogramOperation: OperationDefinition { if ( type === 'date' && @@ -180,7 +179,7 @@ export const dateHistogramOperation: OperationDefinition + <> {!intervalIsRestricted && ( )} - + ); }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx index cc1e23cb82a49..9985ad7229ecc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx @@ -226,6 +226,7 @@ export const FilterList = ({ removeTitle={i18n.translate('xpack.lens.indexPattern.filters.removeFilter', { defaultMessage: 'Remove a filter', })} + isNotRemovable={localFilters.length === 1} > + range.label || + formatter.convert({ + gte: isValidNumber(range.from) ? range.from : FROM_PLACEHOLDER, + lt: isValidNumber(range.to) ? range.to : TO_PLACEHOLDER, + }); + +export const RangePopover = ({ + range, + setRange, + Button, + isOpenByCreation, + setIsOpenByCreation, +}: { + range: LocalRangeType; + setRange: (newRange: LocalRangeType) => void; + Button: React.FunctionComponent<{ onClick: MouseEventHandler }>; + isOpenByCreation: boolean; + setIsOpenByCreation: (open: boolean) => void; + formatter: IFieldFormat; +}) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [tempRange, setTempRange] = useState(range); + + const saveRangeAndReset = (newRange: LocalRangeType, resetRange = false) => { + if (resetRange) { + // reset the temporary range for later use + setTempRange(range); + } + // send the range back to the main state + setRange(newRange); + }; + const { from, to } = tempRange; + + const lteAppendLabel = i18n.translate('xpack.lens.indexPattern.ranges.lessThanOrEqualAppend', { + defaultMessage: '\u2264', + }); + const lteTooltipContent = i18n.translate( + 'xpack.lens.indexPattern.ranges.lessThanOrEqualTooltip', + { + defaultMessage: 'Less than or equal to', + } + ); + const ltPrependLabel = i18n.translate('xpack.lens.indexPattern.ranges.lessThanPrepend', { + defaultMessage: '\u003c', + }); + const ltTooltipContent = i18n.translate('xpack.lens.indexPattern.ranges.lessThanTooltip', { + defaultMessage: 'Less than', + }); + + const onSubmit = () => { + setIsPopoverOpen(false); + setIsOpenByCreation(false); + saveRangeAndReset(tempRange, true); + }; + + return ( + { + setIsPopoverOpen((isOpen) => !isOpen); + setIsOpenByCreation(false); + }} + /> + } + data-test-subj="indexPattern-ranges-popover" + > + + + + { + const newRange = { + ...tempRange, + from: target.value !== '' ? Number(target.value) : -Infinity, + }; + setTempRange(newRange); + saveRangeAndReset(newRange); + }} + append={ + + {lteAppendLabel} + + } + fullWidth + compressed + placeholder={FROM_PLACEHOLDER} + isInvalid={!isValidRange(tempRange)} + /> + + + + + + { + const newRange = { + ...tempRange, + to: target.value !== '' ? Number(target.value) : -Infinity, + }; + setTempRange(newRange); + saveRangeAndReset(newRange); + }} + prepend={ + + {ltPrependLabel} + + } + fullWidth + compressed + placeholder={TO_PLACEHOLDER} + isInvalid={!isValidRange(tempRange)} + onKeyDown={({ key }: React.KeyboardEvent) => { + if (keys.ENTER === key && onSubmit) { + onSubmit(); + } + }} + /> + + + + + ); +}; + +export const AdvancedRangeEditor = ({ + ranges, + setRanges, + onToggleEditor, + formatter, +}: { + ranges: RangeTypeLens[]; + setRanges: (newRanges: RangeTypeLens[]) => void; + onToggleEditor: () => void; + formatter: IFieldFormat; +}) => { + // use a local state to store ids with range objects + const [localRanges, setLocalRanges] = useState(() => + ranges.map((range) => ({ ...range, id: generateId() })) + ); + // we need to force the open state of the popover from the outside in some scenarios + // so we need an extra state here + const [isOpenByCreation, setIsOpenByCreation] = useState(false); + + const lastIndex = localRanges.length - 1; + + // Update locally all the time, but bounce the parents prop function + // to aviod too many requests + useDebounce( + () => { + setRanges(localRanges.map(({ id, ...rest }) => ({ ...rest }))); + }, + TYPING_DEBOUNCE_TIME, + [localRanges] + ); + + const addNewRange = () => { + setLocalRanges([ + ...localRanges, + { + id: generateId(), + from: localRanges[localRanges.length - 1].to, + to: Infinity, + label: '', + }, + ]); + }; + + return ( + + + {' '} + {i18n.translate('xpack.lens.indexPattern.ranges.customIntervalsRemoval', { + defaultMessage: 'Remove custom intervals', + })} + + + } + > + <> + setIsOpenByCreation(false)} + droppableId="RANGES_DROPPABLE_AREA" + items={localRanges} + > + {localRanges.map((range: LocalRangeType, idx: number) => ( + { + const newRanges = localRanges.filter((_, i) => i !== idx); + setLocalRanges(newRanges); + }} + removeTitle={i18n.translate('xpack.lens.indexPattern.ranges.deleteRange', { + defaultMessage: 'Delete range', + })} + isNotRemovable={localRanges.length === 1} + > + { + const newRanges = [...localRanges]; + if (newRange.id === newRanges[idx].id) { + newRanges[idx] = newRange; + } else { + newRanges.push(newRange); + } + setLocalRanges(newRanges); + }} + formatter={formatter} + Button={({ onClick }: { onClick: MouseEventHandler }) => ( + + + {getBetterLabel(range, formatter)} + + + )} + /> + + ))} + + { + addNewRange(); + setIsOpenByCreation(true); + }} + label={i18n.translate('xpack.lens.indexPattern.ranges.addInterval', { + defaultMessage: 'Add interval', + })} + /> + + + ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/constants.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/constants.ts new file mode 100644 index 0000000000000..5c3c3c19a2b0f --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/constants.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; + * you may not use this file except in compliance with the Elastic License. + */ + +export const TYPING_DEBOUNCE_TIME = 256; +// Taken from the Visualize editor +export const FROM_PLACEHOLDER = '\u2212\u221E'; +export const TO_PLACEHOLDER = '+\u221E'; + +export const DEFAULT_INTERVAL = 1000; +export const AUTO_BARS = 'auto'; +export const MIN_HISTOGRAM_BARS = 1; +export const SLICES = 6; + +export const MODES = { + Range: 'range', + Histogram: 'histogram', +} as const; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/index.ts new file mode 100644 index 0000000000000..ccae0c949af0d --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './ranges'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx new file mode 100644 index 0000000000000..5d5acf7778973 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useDebounce } from 'react-use'; +import { + EuiButtonEmpty, + EuiFormRow, + EuiRange, + EuiFlexItem, + EuiFlexGroup, + EuiButtonIcon, + EuiToolTip, +} from '@elastic/eui'; +import { IFieldFormat } from 'src/plugins/data/public'; +import { RangeColumnParams, UpdateParamsFnType, MODES_TYPES } from './ranges'; +import { AdvancedRangeEditor } from './advanced_editor'; +import { TYPING_DEBOUNCE_TIME, MODES, MIN_HISTOGRAM_BARS } from './constants'; + +const BaseRangeEditor = ({ + maxBars, + step, + maxHistogramBars, + onToggleEditor, + onMaxBarsChange, +}: { + maxBars: number; + step: number; + maxHistogramBars: number; + onToggleEditor: () => void; + onMaxBarsChange: (newMaxBars: number) => void; +}) => { + const [maxBarsValue, setMaxBarsValue] = useState(String(maxBars)); + + useDebounce( + () => { + onMaxBarsChange(Number(maxBarsValue)); + }, + TYPING_DEBOUNCE_TIME, + [maxBarsValue] + ); + + const granularityLabel = i18n.translate('xpack.lens.indexPattern.ranges.granularity', { + defaultMessage: 'Granularity', + }); + const decreaseButtonLabel = i18n.translate('xpack.lens.indexPattern.ranges.decreaseButtonLabel', { + defaultMessage: 'Decrease granularity', + }); + const increaseButtonLabel = i18n.translate('xpack.lens.indexPattern.ranges.increaseButtonLabel', { + defaultMessage: 'Increase granularity', + }); + + return ( + <> + + + + + + setMaxBarsValue('' + Math.max(Number(maxBarsValue) - step, MIN_HISTOGRAM_BARS)) + } + aria-label={decreaseButtonLabel} + /> + + + + setMaxBarsValue(currentTarget.value)} + /> + + + + + setMaxBarsValue('' + Math.min(Number(maxBarsValue) + step, maxHistogramBars)) + } + aria-label={increaseButtonLabel} + /> + + + + + + onToggleEditor()}> + {i18n.translate('xpack.lens.indexPattern.ranges.customIntervalsToggle', { + defaultMessage: 'Create custom intervals', + })} + + + ); +}; + +export const RangeEditor = ({ + setParam, + params, + maxHistogramBars, + maxBars, + granularityStep, + onChangeMode, + rangeFormatter, +}: { + params: RangeColumnParams; + maxHistogramBars: number; + maxBars: number; + granularityStep: number; + setParam: UpdateParamsFnType; + onChangeMode: (mode: MODES_TYPES) => void; + rangeFormatter: IFieldFormat; +}) => { + const [isAdvancedEditor, toggleAdvancedEditor] = useState(params.type === MODES.Range); + + // if the maxBars in the params is set to auto refresh it with the default value + // only on bootstrap + useEffect(() => { + if (params.maxBars !== maxBars) { + setParam('maxBars', maxBars); + } + }, [maxBars, params.maxBars, setParam]); + + if (isAdvancedEditor) { + return ( + { + setParam('ranges', ranges); + }} + onToggleEditor={() => { + onChangeMode(MODES.Histogram); + toggleAdvancedEditor(false); + }} + formatter={rangeFormatter} + /> + ); + } + + return ( + { + setParam('maxBars', newMaxBars); + }} + onToggleEditor={() => { + onChangeMode(MODES.Range); + toggleAdvancedEditor(true); + }} + /> + ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx new file mode 100644 index 0000000000000..2409406afcdbc --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -0,0 +1,555 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { EuiFieldNumber, EuiRange, EuiButtonEmpty, EuiLink } from '@elastic/eui'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { IndexPatternPrivateState, IndexPattern } from '../../../types'; +import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; +import { rangeOperation } from '../index'; +import { RangeIndexPatternColumn } from './ranges'; +import { + MODES, + DEFAULT_INTERVAL, + TYPING_DEBOUNCE_TIME, + MIN_HISTOGRAM_BARS, + SLICES, +} from './constants'; +import { RangePopover } from './advanced_editor'; +import { DragDropBuckets } from '../shared_components'; + +const dataPluginMockValue = dataPluginMock.createStartContract(); +// need to overwrite the formatter field first +dataPluginMockValue.fieldFormats.deserialize = jest.fn().mockImplementation(() => { + return { convert: ({ gte, lt }: { gte: string; lt: string }) => `${gte} - ${lt}` }; +}); + +type ReactMouseEvent = React.MouseEvent & + React.MouseEvent; + +const defaultOptions = { + storage: {} as IStorageWrapper, + // need this for MAX_HISTOGRAM value + uiSettings: ({ + get: () => 100, + } as unknown) as IUiSettingsClient, + savedObjectsClient: {} as SavedObjectsClientContract, + dateRange: { + fromDate: 'now-1y', + toDate: 'now', + }, + data: dataPluginMockValue, + http: {} as HttpSetup, +}; + +describe('ranges', () => { + let state: IndexPatternPrivateState; + const InlineOptions = rangeOperation.paramEditor!; + const sourceField = 'MyField'; + const MAX_HISTOGRAM_VALUE = 100; + const GRANULARITY_DEFAULT_VALUE = (MAX_HISTOGRAM_VALUE - MIN_HISTOGRAM_BARS) / 2; + const GRANULARITY_STEP = (MAX_HISTOGRAM_VALUE - MIN_HISTOGRAM_BARS) / SLICES; + + function setToHistogramMode() { + const column = state.layers.first.columns.col1 as RangeIndexPatternColumn; + column.dataType = 'number'; + column.scale = 'interval'; + column.params.type = MODES.Histogram; + } + + function setToRangeMode() { + const column = state.layers.first.columns.col1 as RangeIndexPatternColumn; + column.dataType = 'string'; + column.scale = 'ordinal'; + column.params.type = MODES.Range; + } + + function getDefaultState(): IndexPatternPrivateState { + return { + indexPatternRefs: [], + indexPatterns: {}, + existingFields: {}, + currentIndexPatternId: '1', + isFirstExistenceFetch: false, + layers: { + first: { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + // Start with the histogram type + col1: { + label: sourceField, + dataType: 'number', + operationType: 'range', + scale: 'interval', + isBucketed: true, + sourceField, + params: { + type: MODES.Histogram, + ranges: [{ from: 0, to: DEFAULT_INTERVAL, label: '' }], + maxBars: 'auto', + }, + }, + col2: { + label: 'Count', + dataType: 'number', + isBucketed: false, + sourceField: 'Records', + operationType: 'count', + }, + }, + }, + }, + }; + } + + beforeAll(() => { + jest.useFakeTimers(); + }); + + beforeEach(() => { + state = getDefaultState(); + }); + + describe('toEsAggConfig', () => { + afterAll(() => setToHistogramMode()); + + it('should reflect params correctly', () => { + const esAggsConfig = rangeOperation.toEsAggsConfig( + state.layers.first.columns.col1 as RangeIndexPatternColumn, + 'col1', + {} as IndexPattern + ); + expect(esAggsConfig).toEqual( + expect.objectContaining({ + type: MODES.Histogram, + params: expect.objectContaining({ + field: sourceField, + maxBars: null, + }), + }) + ); + }); + + it('should reflect the type correctly', () => { + setToRangeMode(); + + const esAggsConfig = rangeOperation.toEsAggsConfig( + state.layers.first.columns.col1 as RangeIndexPatternColumn, + 'col1', + {} as IndexPattern + ); + + expect(esAggsConfig).toEqual( + expect.objectContaining({ + type: MODES.Range, + }) + ); + }); + }); + + describe('getPossibleOperationForField', () => { + it('should return operation with the right type for number', () => { + expect( + rangeOperation.getPossibleOperationForField({ + aggregatable: true, + searchable: true, + name: 'test', + displayName: 'test', + type: 'number', + }) + ).toEqual({ + dataType: 'number', + isBucketed: true, + scale: 'interval', + }); + }); + + it('should not return operation if field type is not number', () => { + expect( + rangeOperation.getPossibleOperationForField({ + aggregatable: false, + searchable: true, + name: 'test', + displayName: 'test', + type: 'string', + }) + ).toEqual(undefined); + }); + }); + + describe('paramEditor', () => { + describe('Modify intervals in basic mode', () => { + beforeEach(() => { + state = getDefaultState(); + }); + + it('should start update the state with the default maxBars value', () => { + const setStateSpy = jest.fn(); + mount( + + ); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: GRANULARITY_DEFAULT_VALUE, + }, + }, + }, + }, + }, + }); + }); + + it('should update state when changing Max bars number', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + act(() => { + instance.find(EuiRange).prop('onChange')!( + { + currentTarget: { + value: '' + MAX_HISTOGRAM_VALUE, + }, + } as React.ChangeEvent, + true + ); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: MAX_HISTOGRAM_VALUE, + }, + }, + }, + }, + }, + }); + }); + }); + + it('should update the state using the plus or minus buttons by the step amount', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + act(() => { + // minus button + instance + .find('[data-test-subj="lns-indexPattern-range-maxBars-minus"]') + .find('button') + .prop('onClick')!({} as ReactMouseEvent); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: GRANULARITY_DEFAULT_VALUE - GRANULARITY_STEP, + }, + }, + }, + }, + }, + }); + + // plus button + instance + .find('[data-test-subj="lns-indexPattern-range-maxBars-plus"]') + .find('button') + .prop('onClick')!({} as ReactMouseEvent); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: GRANULARITY_DEFAULT_VALUE, + }, + }, + }, + }, + }, + }); + }); + }); + }); + + describe('Specify range intervals manually', () => { + // @ts-expect-error + window['__react-beautiful-dnd-disable-dev-warnings'] = true; // issue with enzyme & react-beautiful-dnd throwing errors: https://github.com/atlassian/react-beautiful-dnd/issues/1593 + + beforeEach(() => setToRangeMode()); + + it('should show one range interval to start with', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + expect(instance.find(DragDropBuckets).children).toHaveLength(1); + }); + + it('should add a new range', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + // This series of act clojures are made to make it work properly the update flush + act(() => { + instance.find(EuiButtonEmpty).prop('onClick')!({} as ReactMouseEvent); + }); + + act(() => { + // need another wrapping for this in order to work + instance.update(); + + expect(instance.find(RangePopover)).toHaveLength(2); + + // edit the range and check + instance.find(RangePopover).find(EuiFieldNumber).first().prop('onChange')!({ + target: { + value: '50', + }, + } as React.ChangeEvent); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + ranges: [ + { from: 0, to: DEFAULT_INTERVAL, label: '' }, + { from: 50, to: Infinity, label: '' }, + ], + }, + }, + }, + }, + }, + }); + }); + }); + + it('should open a popover to edit an existing range', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + // This series of act clojures are made to make it work properly the update flush + act(() => { + instance.find(RangePopover).find(EuiLink).prop('onClick')!({} as ReactMouseEvent); + }); + + act(() => { + // need another wrapping for this in order to work + instance.update(); + + // edit the range "to" field + instance.find(RangePopover).find(EuiFieldNumber).last().prop('onChange')!({ + target: { + value: '50', + }, + } as React.ChangeEvent); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + ranges: [{ from: 0, to: 50, label: '' }], + }, + }, + }, + }, + }, + }); + }); + }); + + it('should not accept invalid ranges', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + // This series of act clojures are made to make it work properly the update flush + act(() => { + instance.find(RangePopover).find(EuiLink).prop('onClick')!({} as ReactMouseEvent); + }); + + act(() => { + // need another wrapping for this in order to work + instance.update(); + + // edit the range "to" field + instance.find(RangePopover).find(EuiFieldNumber).last().prop('onChange')!({ + target: { + value: '-1', + }, + } as React.ChangeEvent); + }); + + act(() => { + instance.update(); + + // and check + expect(instance.find(RangePopover).find(EuiFieldNumber).last().prop('isInvalid')).toBe( + true + ); + }); + }); + + it('should be possible to remove a range if multiple', () => { + const setStateSpy = jest.fn(); + + // Add an extra range + (state.layers.first.columns.col1 as RangeIndexPatternColumn).params.ranges.push({ + from: DEFAULT_INTERVAL, + to: 2 * DEFAULT_INTERVAL, + label: '', + }); + + const instance = mount( + + ); + + expect(instance.find(RangePopover)).toHaveLength(2); + + // This series of act closures are made to make it work properly the update flush + act(() => { + instance + .find('[data-test-subj="lns-customBucketContainer-remove"]') + .last() + .prop('onClick')!({} as ReactMouseEvent); + }); + + act(() => { + // need another wrapping for this in order to work + instance.update(); + + expect(instance.find(RangePopover)).toHaveLength(1); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx new file mode 100644 index 0000000000000..530c2e962759b --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/common'; +import { Range } from '../../../../../../../../src/plugins/expressions/common/expression_types/index'; +import { RangeEditor } from './range_editor'; +import { OperationDefinition } from '../index'; +import { FieldBasedIndexPatternColumn } from '../column_types'; +import { updateColumnParam, changeColumn } from '../../../state_helpers'; +import { MODES, AUTO_BARS, DEFAULT_INTERVAL, MIN_HISTOGRAM_BARS, SLICES } from './constants'; + +type RangeType = Omit; +export type RangeTypeLens = RangeType & { label: string }; + +export type MODES_TYPES = typeof MODES[keyof typeof MODES]; + +export interface RangeIndexPatternColumn extends FieldBasedIndexPatternColumn { + operationType: 'range'; + params: { + type: MODES_TYPES; + maxBars: typeof AUTO_BARS | number; + ranges: RangeTypeLens[]; + }; +} + +export type RangeColumnParams = RangeIndexPatternColumn['params']; +export type UpdateParamsFnType = ( + paramName: K, + value: RangeColumnParams[K] +) => void; + +export const isValidNumber = (value: number | '') => + value !== '' && !isNaN(value) && isFinite(value); +export const isRangeWithin = (range: RangeTypeLens): boolean => range.from <= range.to; +const isFullRange = ({ from, to }: RangeType) => isValidNumber(from) && isValidNumber(to); +export const isValidRange = (range: RangeTypeLens): boolean => { + if (isFullRange(range)) { + return isRangeWithin(range); + } + return true; +}; + +function getEsAggsParams({ sourceField, params }: RangeIndexPatternColumn) { + if (params.type === MODES.Range) { + return { + field: sourceField, + ranges: params.ranges.filter(isValidRange).map>((range) => { + if (isFullRange(range)) { + return { from: range.from, to: range.to }; + } + const partialRange: Partial = {}; + // be careful with the fields to set on partial ranges + if (isValidNumber(range.from)) { + partialRange.from = range.from; + } + if (isValidNumber(range.to)) { + partialRange.to = range.to; + } + return partialRange; + }), + }; + } + return { + field: sourceField, + // fallback to 0 in case of empty string + maxBars: params.maxBars === AUTO_BARS ? null : params.maxBars, + has_extended_bounds: false, + min_doc_count: 0, + extended_bounds: { min: '', max: '' }, + }; +} + +export const rangeOperation: OperationDefinition = { + type: 'range', + displayName: i18n.translate('xpack.lens.indexPattern.ranges', { + defaultMessage: 'Ranges', + }), + priority: 4, // Higher than terms, so numbers get histogram + getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { + if ( + type === 'number' && + aggregatable && + (!aggregationRestrictions || aggregationRestrictions.range) + ) { + return { + dataType: 'number', + isBucketed: true, + scale: 'interval', + }; + } + }, + buildColumn({ suggestedPriority, field }) { + return { + label: field.name, + dataType: 'number', // string for Range + operationType: 'range', + suggestedPriority, + sourceField: field.name, + isBucketed: true, + scale: 'interval', // ordinal for Range + params: { + type: MODES.Histogram, + ranges: [{ from: 0, to: DEFAULT_INTERVAL, label: '' }], + maxBars: AUTO_BARS, + }, + }; + }, + isTransferable: (column, newIndexPattern) => { + const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField); + + return Boolean( + newField && + newField.type === 'number' && + newField.aggregatable && + (!newField.aggregationRestrictions || newField.aggregationRestrictions.range) + ); + }, + onFieldChange: (oldColumn, indexPattern, field) => { + return { + ...oldColumn, + label: field.name, + sourceField: field.name, + }; + }, + toEsAggsConfig: (column, columnId) => { + const params = getEsAggsParams(column); + return { + id: columnId, + enabled: true, + type: column.params.type, + schema: 'segment', + params, + }; + }, + paramEditor: ({ state, setState, currentColumn, layerId, columnId, uiSettings, data }) => { + const rangeFormatter = data.fieldFormats.deserialize({ id: 'range' }); + const MAX_HISTOGRAM_BARS = uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS); + const granularityStep = (MAX_HISTOGRAM_BARS - MIN_HISTOGRAM_BARS) / SLICES; + const maxBarsDefaultValue = (MAX_HISTOGRAM_BARS - MIN_HISTOGRAM_BARS) / 2; + + // Used to change one param at the time + const setParam: UpdateParamsFnType = (paramName, value) => { + setState( + updateColumnParam({ + state, + layerId, + currentColumn, + paramName, + value, + }) + ); + }; + + // Useful to change more params at once + const onChangeMode = (newMode: MODES_TYPES) => { + const scale = newMode === MODES.Range ? 'ordinal' : 'interval'; + const dataType = newMode === MODES.Range ? 'string' : 'number'; + setState( + changeColumn({ + state, + layerId, + columnId, + newColumn: { + ...currentColumn, + scale, + dataType, + params: { + type: newMode, + ranges: [{ from: 0, to: DEFAULT_INTERVAL, label: '' }], + maxBars: maxBarsDefaultValue, + }, + }, + keepParams: false, + }) + ); + }; + return ( + + ); + }, +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx index 73378cea919a6..47380f7865578 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx @@ -35,6 +35,7 @@ interface BucketContainerProps { invalidMessage: string; onRemoveClick: () => void; removeTitle: string; + isNotRemovable?: boolean; children: React.ReactNode; dataTestSubj?: string; } @@ -46,6 +47,7 @@ const BucketContainer = ({ removeTitle, children, dataTestSubj, + isNotRemovable, }: BucketContainerProps) => { return ( @@ -75,6 +77,7 @@ const BucketContainer = ({ onClick={onRemoveClick} aria-label={removeTitle} title={removeTitle} + disabled={isNotRemovable} />
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx index 20c421008a746..c1a87a2013747 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiForm, EuiFormRow, EuiRange, EuiSelect } from '@elastic/eui'; +import { EuiFormRow, EuiRange, EuiSelect } from '@elastic/eui'; import { IndexPatternColumn } from '../../indexpattern'; import { updateColumnParam } from '../../state_helpers'; import { DataType } from '../../../types'; @@ -171,7 +171,7 @@ export const termsOperation: OperationDefinition = { }), }); return ( - + <> = { })} /> - + ); }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts index 4ac3fc89500f9..703431f724c5d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts @@ -225,29 +225,43 @@ describe('getOperationTypesForField', () => { it('should list out all field-operation tuples for different operation meta data', () => { expect(getAvailableOperationsByMetadata(expectedIndexPatterns[1])).toMatchInlineSnapshot(` Array [ + Object { + "operationMetaData": Object { + "dataType": "date", + "isBucketed": true, + "scale": "interval", + }, + "operations": Array [ + Object { + "field": "timestamp", + "operationType": "date_histogram", + "type": "field", + }, + ], + }, Object { "operationMetaData": Object { "dataType": "number", "isBucketed": true, - "scale": "ordinal", + "scale": "interval", }, "operations": Array [ Object { "field": "bytes", - "operationType": "terms", + "operationType": "range", "type": "field", }, ], }, Object { "operationMetaData": Object { - "dataType": "string", + "dataType": "number", "isBucketed": true, "scale": "ordinal", }, "operations": Array [ Object { - "field": "source", + "field": "bytes", "operationType": "terms", "type": "field", }, @@ -255,14 +269,14 @@ describe('getOperationTypesForField', () => { }, Object { "operationMetaData": Object { - "dataType": "date", + "dataType": "string", "isBucketed": true, - "scale": "interval", + "scale": "ordinal", }, "operations": Array [ Object { - "field": "timestamp", - "operationType": "date_histogram", + "field": "source", + "operationType": "terms", "type": "field", }, ], diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index b691c5b5c4c40..a3c0e8aed7421 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -26,6 +26,7 @@ export interface IndexPattern { export type IndexPatternField = IFieldType & { displayName: string; aggregationRestrictions?: Partial; + meta?: boolean; }; export interface IndexPatternLayer { diff --git a/x-pack/plugins/lens/public/lens_attribute_service.ts b/x-pack/plugins/lens/public/lens_attribute_service.ts new file mode 100644 index 0000000000000..3c43fd98cceb4 --- /dev/null +++ b/x-pack/plugins/lens/public/lens_attribute_service.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart } from '../../../../src/core/public'; +import { LensPluginStartDependencies } from './plugin'; +import { AttributeService } from '../../../../src/plugins/dashboard/public'; +import { + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput, +} from './editor_frame_service/embeddable/embeddable'; +import { SavedObjectIndexStore, DOC_TYPE } from './persistence'; + +export type LensAttributeService = AttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput +>; + +export function getLensAttributeService( + core: CoreStart, + startDependencies: LensPluginStartDependencies +): LensAttributeService { + const savedObjectStore = new SavedObjectIndexStore(core.savedObjects.client); + return startDependencies.dashboard.getAttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >(DOC_TYPE, { + customSaveMethod: async ( + type: string, + attributes: LensSavedObjectAttributes, + savedObjectId?: string + ) => { + const savedDoc = await savedObjectStore.save({ + ...attributes, + savedObjectId, + type: DOC_TYPE, + }); + return { id: savedDoc.savedObjectId }; + }, + customUnwrapMethod: (savedObject) => { + return { + ...savedObject.attributes, + references: savedObject.references, + }; + }, + }); +} diff --git a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts index 8bb1e086a37c2..fa7747dd18e42 100644 --- a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts +++ b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts @@ -83,7 +83,7 @@ describe('Lens UI telemetry', () => { jest.runOnlyPendingTimers(); - expect(http.post).toHaveBeenCalledWith(`/api/lens/telemetry`, { + expect(http.post).toHaveBeenCalledWith(`/api/lens/stats`, { body: JSON.stringify({ events: { '2019-10-23': { diff --git a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts index cb517acff4f7a..8f9ce7f2ceab8 100644 --- a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts +++ b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts @@ -86,7 +86,7 @@ export class LensReportManager { this.readFromStorage(); if (Object.keys(this.events).length || Object.keys(this.suggestionEvents).length) { try { - await this.http.post(`${BASE_API_URL}/telemetry`, { + await this.http.post(`${BASE_API_URL}/stats`, { body: JSON.stringify({ events: this.events, suggestionEvents: this.suggestionEvents, diff --git a/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts b/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts index ba7c0ee6ae786..6b6f81aeefed0 100644 --- a/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts +++ b/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts @@ -42,7 +42,7 @@ describe('LensStore', () => { }); expect(doc).toEqual({ - id: 'FOO', + savedObjectId: 'FOO', title: 'Hello', description: 'My doc', visualizationType: 'bar', @@ -82,7 +82,7 @@ describe('LensStore', () => { test('updates and returns a visualization document', async () => { const { client, store } = testStore(); const doc = await store.save({ - id: 'Gandalf', + savedObjectId: 'Gandalf', title: 'Even the very wise cannot see all ends.', visualizationType: 'line', references: [], @@ -95,7 +95,7 @@ describe('LensStore', () => { }); expect(doc).toEqual({ - id: 'Gandalf', + savedObjectId: 'Gandalf', title: 'Even the very wise cannot see all ends.', visualizationType: 'line', references: [], diff --git a/x-pack/plugins/lens/public/persistence/saved_object_store.ts b/x-pack/plugins/lens/public/persistence/saved_object_store.ts index e4609213ec792..c6b3fd2cc0f65 100644 --- a/x-pack/plugins/lens/public/persistence/saved_object_store.ts +++ b/x-pack/plugins/lens/public/persistence/saved_object_store.ts @@ -13,7 +13,7 @@ import { Query } from '../../../../../src/plugins/data/public'; import { PersistableFilter } from '../../common'; export interface Document { - id?: string; + savedObjectId?: string; type?: string; visualizationType: string | null; title: string; @@ -30,11 +30,11 @@ export interface Document { export const DOC_TYPE = 'lens'; export interface DocumentSaver { - save: (vis: Document) => Promise<{ id: string }>; + save: (vis: Document) => Promise<{ savedObjectId: string }>; } export interface DocumentLoader { - load: (id: string) => Promise; + load: (savedObjectId: string) => Promise; } export type SavedObjectStore = DocumentLoader & DocumentSaver; @@ -46,20 +46,20 @@ export class SavedObjectIndexStore implements SavedObjectStore { this.client = client; } - async save(vis: Document) { - const { id, type, references, ...rest } = vis; + save = async (vis: Document) => { + const { savedObjectId, type, references, ...rest } = vis; // TODO: SavedObjectAttributes should support this kind of object, // remove this workaround when SavedObjectAttributes is updated. const attributes = (rest as unknown) as SavedObjectAttributes; - const result = await (id - ? this.safeUpdate(id, attributes, references) + const result = await (savedObjectId + ? this.safeUpdate(savedObjectId, attributes, references) : this.client.create(DOC_TYPE, attributes, { references, })); - return { ...vis, id: result.id }; - } + return { ...vis, savedObjectId: result.id }; + }; // As Lens is using an object to store its attributes, using the update API // will merge the new attribute object with the old one, not overwriting deleted @@ -68,7 +68,7 @@ export class SavedObjectIndexStore implements SavedObjectStore { // This function fixes this by doing two updates - one to empty out the document setting // every key to null, and a second one to load the new content. private async safeUpdate( - id: string, + savedObjectId: string, attributes: SavedObjectAttributes, references: SavedObjectReference[] ) { @@ -78,14 +78,14 @@ export class SavedObjectIndexStore implements SavedObjectStore { }); return ( await this.client.bulkUpdate([ - { type: DOC_TYPE, id, attributes: resetAttributes, references }, - { type: DOC_TYPE, id, attributes, references }, + { type: DOC_TYPE, id: savedObjectId, attributes: resetAttributes, references }, + { type: DOC_TYPE, id: savedObjectId, attributes, references }, ]) ).savedObjects[1]; } - async load(id: string): Promise { - const { type, attributes, references, error } = await this.client.get(DOC_TYPE, id); + async load(savedObjectId: string): Promise { + const { type, attributes, references, error } = await this.client.get(DOC_TYPE, savedObjectId); if (error) { throw error; @@ -94,7 +94,7 @@ export class SavedObjectIndexStore implements SavedObjectStore { return { ...(attributes as SavedObjectAttributes), references, - id, + savedObjectId, type, } as Document; } diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index f9c63f54d6713..1655a571721f5 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -7,10 +7,12 @@ import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public'; import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; +import { DashboardStart } from 'src/plugins/dashboard/public'; import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public'; import { VisualizationsSetup } from 'src/plugins/visualizations/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { UrlForwardingSetup } from 'src/plugins/url_forwarding/public'; +import { GlobalSearchPluginSetup } from '../../global_search/public'; import { ChartsPluginSetup } from '../../../../src/plugins/charts/public'; import { EditorFrameService } from './editor_frame_service'; import { @@ -31,8 +33,10 @@ import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common'; import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; +import { getSearchProvider } from './search_provider'; import './index.scss'; +import { getLensAttributeService, LensAttributeService } from './lens_attribute_service'; export interface LensPluginSetupDependencies { urlForwarding: UrlForwardingSetup; @@ -41,6 +45,7 @@ export interface LensPluginSetupDependencies { embeddable?: EmbeddableSetup; visualizations: VisualizationsSetup; charts: ChartsPluginSetup; + globalSearch?: GlobalSearchPluginSetup; } export interface LensPluginStartDependencies { @@ -48,13 +53,14 @@ export interface LensPluginStartDependencies { expressions: ExpressionsStart; navigation: NavigationPublicPluginStart; uiActions: UiActionsStart; - embeddable: EmbeddableStart; + dashboard: DashboardStart; + embeddable?: EmbeddableStart; } - export class LensPlugin { private datatableVisualization: DatatableVisualization; private editorFrameService: EditorFrameService; private createEditorFrame: EditorFrameStart['createInstance'] | null = null; + private attributeService: LensAttributeService | null = null; private indexpatternDatasource: IndexPatternDatasource; private xyVisualization: XyVisualization; private metricVisualization: MetricVisualization; @@ -78,13 +84,18 @@ export class LensPlugin { embeddable, visualizations, charts, + globalSearch, }: LensPluginSetupDependencies ) { - const editorFrameSetupInterface = this.editorFrameService.setup(core, { - data, - embeddable, - expressions, - }); + const editorFrameSetupInterface = this.editorFrameService.setup( + core, + { + data, + embeddable, + expressions, + }, + () => this.attributeService! + ); const dependencies: IndexPatternDatasourceSetupPlugins & XyVisualizationPluginSetupPlugins & DatatableVisualizationPluginSetupPlugins & @@ -106,20 +117,44 @@ export class LensPlugin { visualizations.registerAlias(getLensAliasConfig()); + const getByValueFeatureFlag = async () => { + const [, deps] = await core.getStartServices(); + return deps.dashboard.dashboardFeatureFlagConfig; + }; + core.application.register({ id: 'lens', title: NOT_INTERNATIONALIZED_PRODUCT_NAME, navLinkStatus: AppNavLinkStatus.hidden, mount: async (params: AppMountParameters) => { const { mountApp } = await import('./app_plugin/mounter'); - return mountApp(core, params, this.createEditorFrame!); + return mountApp(core, params, { + createEditorFrame: this.createEditorFrame!, + attributeService: this.attributeService!, + getByValueFeatureFlag, + }); }, }); + if (globalSearch) { + globalSearch.registerResultProvider( + getSearchProvider( + core.getStartServices().then( + ([ + { + application: { capabilities }, + }, + ]) => capabilities + ) + ) + ); + } + urlForwarding.forwardApp('lens', 'lens'); } start(core: CoreStart, startDependencies: LensPluginStartDependencies) { + this.attributeService = getLensAttributeService(core, startDependencies); this.createEditorFrame = this.editorFrameService.start(core, startDependencies).createInstance; } diff --git a/x-pack/plugins/lens/public/search_provider.ts b/x-pack/plugins/lens/public/search_provider.ts new file mode 100644 index 0000000000000..c19e7970b45ae --- /dev/null +++ b/x-pack/plugins/lens/public/search_provider.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import levenshtein from 'js-levenshtein'; +import { ApplicationStart } from 'kibana/public'; +import { from } from 'rxjs'; +import { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; +import { GlobalSearchResultProvider } from '../../global_search/public'; +import { getFullPath } from '../common'; + +/** + * Global search provider adding a Lens entry. + * This is necessary because Lens does not show up in the nav bar and is filtered out by the + * default app provider. + * + * It is inlining the same search term matching logic as the application search provider. + * + * TODO: This is a workaround and can be removed once there is a generic way to register sub features + * of apps. In this case, Lens should be considered a feature of Visualize. + */ +export const getSearchProvider: ( + uiCapabilities: Promise +) => GlobalSearchResultProvider = (uiCapabilities) => ({ + id: 'lens', + find: (term) => { + return from( + uiCapabilities.then(({ navLinks: { visualize: visualizeNavLink } }) => { + if (!visualizeNavLink) { + return []; + } + const title = i18n.translate('xpack.lens.searchTitle', { + defaultMessage: 'Lens: create visualizations', + description: 'Lens is a product name and should not be translated', + }); + const searchableTitle = title.toLowerCase(); + + term = term.toLowerCase(); + let score = 0; + + // shortcuts to avoid calculating the distance when there is an exact match somewhere. + if (searchableTitle === term) { + score = 100; + } else if (searchableTitle.startsWith(term)) { + score = 90; + } else if (searchableTitle.includes(term)) { + score = 75; + } else { + const length = Math.max(term.length, searchableTitle.length); + const distance = levenshtein(term, searchableTitle); + + // maximum lev distance is length, we compute the match ratio (lower distance is better) + const ratio = Math.floor((1 - distance / length) * 100); + if (ratio >= 60) { + score = ratio; + } + } + if (score === 0) return []; + return [ + { + id: 'lens', + title, + type: 'application', + icon: 'logoKibana', + meta: { + categoryId: DEFAULT_APP_CATEGORIES.kibana.id, + categoryLabel: DEFAULT_APP_CATEGORIES.kibana.label, + }, + score, + url: getFullPath(), + }, + ]; + }) + ); + }, +}); diff --git a/x-pack/plugins/lens/public/visualization_container.scss b/x-pack/plugins/lens/public/visualization_container.scss index e5c359112fe4b..59ddbf4bf6478 100644 --- a/x-pack/plugins/lens/public/visualization_container.scss +++ b/x-pack/plugins/lens/public/visualization_container.scss @@ -1,3 +1,4 @@ .lnsVisualizationContainer { + @include euiScrollBar; overflow: auto; -} \ No newline at end of file +} diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx index c7781c2e1d50c..ee22ee51301df 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx @@ -17,7 +17,6 @@ import { EuiFormRow, EuiText, htmlIdGenerator, - EuiForm, EuiColorPicker, EuiColorPickerProps, EuiToolTip, @@ -366,7 +365,7 @@ export function DimensionEditor(props: VisualizationDimensionEditorProps) 'auto'; return ( - + <> ) }} /> - + ); } diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx index 9379c8a612eb2..24bf78dba2121 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx @@ -24,6 +24,7 @@ import { ExpressionFunctionDefinition, ExpressionRenderDefinition, ExpressionValueSearchContext, + KibanaDatatable, } from 'src/plugins/expressions/public'; import { IconType } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -251,6 +252,12 @@ export function XYChart({ ({ id }) => id === filteredLayers[0].xAccessor ); const xAxisFormatter = formatFactory(xAxisColumn && xAxisColumn.formatHint); + const layersAlreadyFormatted: Record = {}; + // This is a safe formatter for the xAccessor that abstracts the knowledge of already formatted layers + const safeXAccessorLabelRenderer = (value: unknown): string => + xAxisColumn && layersAlreadyFormatted[xAxisColumn.id] + ? (value as string) + : xAxisFormatter.convert(value); const chartHasMoreThanOneSeries = filteredLayers.length > 1 || @@ -364,7 +371,7 @@ export function XYChart({ theme={chartTheme} baseTheme={chartBaseTheme} tooltip={{ - headerFormatter: (d) => xAxisFormatter.convert(d.value), + headerFormatter: (d) => safeXAccessorLabelRenderer(d.value), }} rotation={shouldRotate ? 90 : 0} xDomain={xDomain} @@ -409,9 +416,15 @@ export function XYChart({ const points = [ { - row: table.rows.findIndex( - (row) => layer.xAccessor && row[layer.xAccessor] === xyGeometry.x - ), + row: table.rows.findIndex((row) => { + if (layer.xAccessor) { + if (layersAlreadyFormatted[layer.xAccessor]) { + // stringify the value to compare with the chart value + return xAxisFormatter.convert(row[layer.xAccessor]) === xyGeometry.x; + } + return row[layer.xAccessor] === xyGeometry.x; + } + }), column: table.columns.findIndex((col) => col.id === layer.xAccessor), value: xyGeometry.x, }, @@ -455,7 +468,7 @@ export function XYChart({ strokeWidth: 2, }} hide={filteredLayers[0].hide || !filteredLayers[0].xAccessor} - tickFormat={(d) => xAxisFormatter.convert(d)} + tickFormat={(d) => safeXAccessorLabelRenderer(d)} style={{ tickLabel: { visible: tickLabelsVisibilitySettings?.x, @@ -504,9 +517,43 @@ export function XYChart({ const table = data.tables[layerId]; + const isPrimitive = (value: unknown): boolean => + value != null && typeof value !== 'object'; + + // what if row values are not primitive? That is the case of, for instance, Ranges + // remaps them to their serialized version with the formatHint metadata + // In order to do it we need to make a copy of the table as the raw one is required for more features (filters, etc...) later on + const tableConverted: KibanaDatatable = { + ...table, + rows: table.rows.map((row) => { + const newRow = { ...row }; + for (const column of table.columns) { + const record = newRow[column.id]; + if (record && !isPrimitive(record)) { + newRow[column.id] = formatFactory(column.formatHint).convert(record); + } + } + return newRow; + }), + }; + + // save the id of the layer with the custom table + table.columns.reduce>( + (alreadyFormatted: Record, { id }) => { + if (alreadyFormatted[id]) { + return alreadyFormatted; + } + alreadyFormatted[id] = table.rows.some( + (row, i) => row[id] !== tableConverted.rows[i][id] + ); + return alreadyFormatted; + }, + layersAlreadyFormatted + ); + // For date histogram chart type, we're getting the rows that represent intervals without data. // To not display them in the legend, they need to be filtered out. - const rows = table.rows.filter( + const rows = tableConverted.rows.filter( (row) => !(xAccessor && typeof row[xAccessor] === 'undefined') && !( @@ -559,19 +606,28 @@ export function XYChart({ // * Key - Y name // * Formatted value - Y name if (accessors.length > 1) { - return d.seriesKeys + const result = d.seriesKeys .map((key: string | number, i) => { - if (i === 0 && splitHint) { + if ( + i === 0 && + splitHint && + splitAccessor && + !layersAlreadyFormatted[splitAccessor] + ) { return formatFactory(splitHint).convert(key); } return splitAccessor && i === 0 ? key : columnToLabelMap[key] ?? ''; }) .join(' - '); + return result; } // For formatted split series, format the key // This handles splitting by dates, for example if (splitHint) { + if (splitAccessor && layersAlreadyFormatted[splitAccessor]) { + return d.seriesKeys[0]; + } return formatFactory(splitHint).convert(d.seriesKeys[0]); } // This handles both split and single-y cases: diff --git a/x-pack/plugins/lens/server/routes/existing_fields.test.ts b/x-pack/plugins/lens/server/routes/existing_fields.test.ts index 728b78c8e97bc..9799dcf92ae41 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.test.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.test.ts @@ -15,6 +15,7 @@ describe('existingFields', () => { name, isScript: false, isAlias: false, + isMeta: false, path: name.split('.'), ...obj, }; @@ -101,6 +102,15 @@ describe('existingFields', () => { expect(result).toEqual(['baz']); }); + + it('supports meta fields', () => { + const result = existingFields( + [{ _mymeta: 'abc', ...indexPattern({}, { bar: 'scriptvalue' }) }], + [field({ name: '_mymeta', isMeta: true, path: ['_mymeta'] })] + ); + + expect(result).toEqual(['_mymeta']); + }); }); describe('buildFieldList', () => { @@ -116,6 +126,7 @@ describe('buildFieldList', () => { { name: 'bar' }, { name: '@bar' }, { name: 'baz' }, + { name: '_mymeta' }, ]), }, references: [], @@ -142,7 +153,7 @@ describe('buildFieldList', () => { ]; it('uses field descriptors to determine the path', () => { - const fields = buildFieldList(indexPattern, mappings, fieldDescriptors); + const fields = buildFieldList(indexPattern, mappings, fieldDescriptors, []); expect(fields.find((f) => f.name === 'baz')).toMatchObject({ isAlias: false, isScript: false, @@ -152,7 +163,7 @@ describe('buildFieldList', () => { }); it('uses aliases to determine the path', () => { - const fields = buildFieldList(indexPattern, mappings, fieldDescriptors); + const fields = buildFieldList(indexPattern, mappings, fieldDescriptors, []); expect(fields.find((f) => f.isAlias)).toMatchObject({ isAlias: true, isScript: false, @@ -162,7 +173,7 @@ describe('buildFieldList', () => { }); it('supports scripted fields', () => { - const fields = buildFieldList(indexPattern, mappings, fieldDescriptors); + const fields = buildFieldList(indexPattern, mappings, fieldDescriptors, []); expect(fields.find((f) => f.isScript)).toMatchObject({ isAlias: false, isScript: true, @@ -173,13 +184,24 @@ describe('buildFieldList', () => { }); }); + it('supports meta fields', () => { + const fields = buildFieldList(indexPattern, mappings, fieldDescriptors, ['_mymeta']); + expect(fields.find((f) => f.isMeta)).toMatchObject({ + isAlias: false, + isScript: false, + isMeta: true, + name: '_mymeta', + path: ['_mymeta'], + }); + }); + it('handles missing mappings', () => { - const fields = buildFieldList(indexPattern, {}, fieldDescriptors); + const fields = buildFieldList(indexPattern, {}, fieldDescriptors, []); expect(fields.every((f) => f.isAlias === false)).toEqual(true); }); it('handles empty fieldDescriptors by skipping multi-mappings', () => { - const fields = buildFieldList(indexPattern, mappings, []); + const fields = buildFieldList(indexPattern, mappings, [], []); expect(fields.find((f) => f.name === 'baz')).toMatchObject({ isAlias: false, isScript: false, diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index 7ab3cdceb2145..33fcafacfad73 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -12,6 +12,7 @@ import { BASE_API_URL } from '../../common'; import { IndexPatternsFetcher, IndexPatternAttributes, + UI_SETTINGS, } from '../../../../../src/plugins/data/server'; /** @@ -36,13 +37,12 @@ export interface Field { name: string; isScript: boolean; isAlias: boolean; + isMeta: boolean; path: string[]; lang?: string; script?: string; } -const metaFields = ['_source', '_type']; - export async function existingFieldsRoute(setup: CoreSetup) { const router = setup.http.createRouter(); @@ -104,14 +104,15 @@ async function fetchFieldExistence({ toDate?: string; timeFieldName?: string; }) { + const metaFields: string[] = await context.core.uiSettings.client.get(UI_SETTINGS.META_FIELDS); const { indexPattern, indexPatternTitle, mappings, fieldDescriptors, - } = await fetchIndexPatternDefinition(indexPatternId, context); + } = await fetchIndexPatternDefinition(indexPatternId, context, metaFields); - const fields = buildFieldList(indexPattern, mappings, fieldDescriptors); + const fields = buildFieldList(indexPattern, mappings, fieldDescriptors, metaFields); const docs = await fetchIndexPatternStats({ fromDate, toDate, @@ -128,7 +129,11 @@ async function fetchFieldExistence({ }; } -async function fetchIndexPatternDefinition(indexPatternId: string, context: RequestHandlerContext) { +async function fetchIndexPatternDefinition( + indexPatternId: string, + context: RequestHandlerContext, + metaFields: string[] +) { const savedObjectsClient = context.core.savedObjects.client; const requestClient = context.core.elasticsearch.legacy.client; const indexPattern = await savedObjectsClient.get( @@ -178,7 +183,8 @@ async function fetchIndexPatternDefinition(indexPatternId: string, context: Requ export function buildFieldList( indexPattern: SavedObject, mappings: MappingResult | {}, - fieldDescriptors: FieldDescriptor[] + fieldDescriptors: FieldDescriptor[], + metaFields: string[] ): Field[] { const aliasMap = Object.entries(Object.values(mappings)[0]?.mappings.properties ?? {}) .map(([name, v]) => ({ ...v, name })) @@ -204,6 +210,9 @@ export function buildFieldList( path: path.split('.'), lang: field.lang, script: field.script, + // id is a special case - it doesn't show up in the meta field list, + // but as it's not part of source, it has to be handled separately. + isMeta: metaFields.includes(field.name) || field.name === '_id', }; } ); @@ -312,7 +321,7 @@ function exists(obj: unknown, path: string[], i = 0): boolean { * Exported only for unit tests. */ export function existingFields( - docs: Array<{ _source: unknown; fields: unknown }>, + docs: Array<{ _source: unknown; fields: unknown; [key: string]: unknown }>, fields: Field[] ): string[] { const missingFields = new Set(fields); @@ -323,7 +332,14 @@ export function existingFields( } missingFields.forEach((field) => { - if (exists(field.isScript ? doc.fields : doc._source, field.path)) { + let fieldStore = doc._source; + if (field.isScript) { + fieldStore = doc.fields; + } + if (field.isMeta) { + fieldStore = doc; + } + if (exists(fieldStore, field.path)) { missingFields.delete(field); } }); diff --git a/x-pack/plugins/lens/server/routes/telemetry.ts b/x-pack/plugins/lens/server/routes/telemetry.ts index 7925416ff5df2..06a7091104871 100644 --- a/x-pack/plugins/lens/server/routes/telemetry.ts +++ b/x-pack/plugins/lens/server/routes/telemetry.ts @@ -15,7 +15,7 @@ export async function initLensUsageRoute(setup: CoreSetup) { const router = setup.http.createRouter(); router.post( { - path: `${BASE_API_URL}/telemetry`, + path: `${BASE_API_URL}/stats`, validate: { body: schema.object({ events: schema.mapOf(schema.string(), schema.mapOf(schema.string(), schema.number())), diff --git a/x-pack/plugins/lens/server/usage/collectors.ts b/x-pack/plugins/lens/server/usage/collectors.ts index 3f033bd3b03d0..c32fc0371ed8a 100644 --- a/x-pack/plugins/lens/server/usage/collectors.ts +++ b/x-pack/plugins/lens/server/usage/collectors.ts @@ -10,6 +10,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { TaskManagerStartContract } from '../../../task_manager/server'; import { LensUsage, LensTelemetryState } from './types'; +import { lensUsageSchema } from './schema'; export function registerLensUsageCollector( usageCollection: UsageCollectionSetup, @@ -20,9 +21,9 @@ export function registerLensUsageCollector( // mark lensUsageCollector as ready to collect when the TaskManager is ready isCollectorReady = true; }); - const lensUsageCollector = usageCollection.makeUsageCollector({ + const lensUsageCollector = usageCollection.makeUsageCollector({ type: 'lens', - fetch: async (): Promise => { + async fetch() { try { const docs = await getLatestTaskState(await taskManager); // get the accumulated state from the recurring task @@ -55,6 +56,7 @@ export function registerLensUsageCollector( } }, isReady: () => isCollectorReady, + schema: lensUsageSchema, }); usageCollection.registerCollector(lensUsageCollector); diff --git a/x-pack/plugins/lens/server/usage/schema.ts b/x-pack/plugins/lens/server/usage/schema.ts new file mode 100644 index 0000000000000..a35d4d91845ee --- /dev/null +++ b/x-pack/plugins/lens/server/usage/schema.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MakeSchemaFrom } from 'src/plugins/usage_collection/server'; +import { LensUsage } from './types'; + +const eventsSchema: MakeSchemaFrom = { + app_query_change: { type: 'long' }, + indexpattern_field_info_click: { type: 'long' }, + loaded: { type: 'long' }, + app_filters_updated: { type: 'long' }, + app_date_change: { type: 'long' }, + save_failed: { type: 'long' }, + loaded_404: { type: 'long' }, + drop_total: { type: 'long' }, + chart_switch: { type: 'long' }, + suggestion_confirmed: { type: 'long' }, + suggestion_clicked: { type: 'long' }, + drop_onto_workspace: { type: 'long' }, + drop_non_empty: { type: 'long' }, + drop_empty: { type: 'long' }, + indexpattern_changed: { type: 'long' }, + indexpattern_filters_cleared: { type: 'long' }, + indexpattern_type_filter_toggled: { type: 'long' }, + indexpattern_existence_toggled: { type: 'long' }, + indexpattern_show_all_fields_clicked: { type: 'long' }, + drop_onto_dimension: { type: 'long' }, + indexpattern_dimension_removed: { type: 'long' }, + indexpattern_dimension_field_changed: { type: 'long' }, + xy_change_layer_display: { type: 'long' }, + xy_layer_removed: { type: 'long' }, + xy_layer_added: { type: 'long' }, + indexpattern_dimension_operation_terms: { type: 'long' }, + indexpattern_dimension_operation_date_histogram: { type: 'long' }, + indexpattern_dimension_operation_avg: { type: 'long' }, + indexpattern_dimension_operation_min: { type: 'long' }, + indexpattern_dimension_operation_max: { type: 'long' }, + indexpattern_dimension_operation_sum: { type: 'long' }, + indexpattern_dimension_operation_count: { type: 'long' }, + indexpattern_dimension_operation_cardinality: { type: 'long' }, + indexpattern_dimension_operation_filters: { type: 'long' }, +}; + +const suggestionEventsSchema: MakeSchemaFrom = { + back_to_current: { type: 'long' }, + reload: { type: 'long' }, +}; + +const savedSchema: MakeSchemaFrom = { + bar: { type: 'long' }, + bar_horizontal: { type: 'long' }, + line: { type: 'long' }, + area: { type: 'long' }, + bar_stacked: { type: 'long' }, + bar_percentage_stacked: { type: 'long' }, + bar_horizontal_stacked: { type: 'long' }, + bar_horizontal_percentage_stacked: { type: 'long' }, + area_stacked: { type: 'long' }, + area_percentage_stacked: { type: 'long' }, + lnsDatatable: { type: 'long' }, + lnsPie: { type: 'long' }, + lnsMetric: { type: 'long' }, +}; + +export const lensUsageSchema: MakeSchemaFrom = { + // LensClickUsage + events_30_days: eventsSchema, + events_90_days: eventsSchema, + suggestion_events_30_days: suggestionEventsSchema, + suggestion_events_90_days: suggestionEventsSchema, + + // LensVisualizationUsage + saved_overall_total: { type: 'long' }, + saved_30_days_total: { type: 'long' }, + saved_90_days_total: { type: 'long' }, + + saved_overall: savedSchema, + saved_30_days: savedSchema, + saved_90_days: savedSchema, +}; diff --git a/x-pack/plugins/licensing/public/services/feature_usage_service.ts b/x-pack/plugins/licensing/public/services/feature_usage_service.ts index c076bff58359f..d8fe892a2f684 100644 --- a/x-pack/plugins/licensing/public/services/feature_usage_service.ts +++ b/x-pack/plugins/licensing/public/services/feature_usage_service.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isDate from 'lodash/isDate'; import type { HttpSetup, HttpStart } from 'src/core/public'; import { LicenseType } from '../../common/types'; diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index d72d04d2a1843..be891b6e59608 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -37,6 +37,7 @@ export const FONTS_API_PATH = `${GIS_API_PATH}/fonts`; export const API_ROOT_PATH = `/${GIS_API_PATH}`; export const MVT_GETTILE_API_PATH = 'mvt/getTile'; +export const MVT_GETGRIDTILE_API_PATH = 'mvt/getGridTile'; export const MVT_SOURCE_LAYER_NAME = 'source_layer'; export const KBN_TOO_MANY_FEATURES_PROPERTY = '__kbn_too_many_features__'; export const KBN_TOO_MANY_FEATURES_IMAGE_ID = '__kbn_too_many_features_image_id__'; @@ -165,8 +166,13 @@ export enum GRID_RESOLUTION { COARSE = 'COARSE', FINE = 'FINE', MOST_FINE = 'MOST_FINE', + SUPER_FINE = 'SUPER_FINE', } +export const SUPER_FINE_ZOOM_DELTA = 7; // (2 ^ SUPER_FINE_ZOOM_DELTA) ^ 2 = number of cells in a given tile +export const GEOTILE_GRID_AGG_NAME = 'gridSplit'; +export const GEOCENTROID_AGG_NAME = 'gridCentroid'; + export const TOP_TERM_PERCENTAGE_SUFFIX = '__percentage'; export const COUNT_PROP_LABEL = i18n.translate('xpack.maps.aggs.defaultCountLabel', { @@ -230,8 +236,6 @@ export enum SCALING_TYPES { MVT = 'MVT', } -export const RGBA_0000 = 'rgba(0,0,0,0)'; - export enum MVT_FIELD_TYPE { STRING = 'String', NUMBER = 'Number', diff --git a/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.d.ts b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.d.ts new file mode 100644 index 0000000000000..b1c1b181d8130 --- /dev/null +++ b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.d.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Feature } from 'geojson'; +import { RENDER_AS } from '../constants'; + +export function convertCompositeRespToGeoJson(esResponse: any, renderAs: RENDER_AS): Feature[]; +export function convertRegularRespToGeoJson(esResponse: any, renderAs: RENDER_AS): Feature[]; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.js b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.js similarity index 79% rename from x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.js rename to x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.js index 35dbebdfd3c8a..a8f32bb4e7f5d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.js +++ b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.js @@ -5,12 +5,12 @@ */ import _ from 'lodash'; -import { RENDER_AS } from '../../../../common/constants'; -import { getTileBoundingBox } from './geo_tile_utils'; -import { extractPropertiesFromBucket } from '../../util/es_agg_utils'; -import { clamp } from '../../../../common/elasticsearch_geo_utils'; +import { RENDER_AS, GEOTILE_GRID_AGG_NAME, GEOCENTROID_AGG_NAME } from '../constants'; +import { getTileBoundingBox } from '../geo_tile_utils'; +import { extractPropertiesFromBucket } from './es_agg_utils'; +import { clamp } from './elasticsearch_geo_utils'; -const GRID_BUCKET_KEYS_TO_IGNORE = ['key', 'gridCentroid']; +const GRID_BUCKET_KEYS_TO_IGNORE = ['key', GEOCENTROID_AGG_NAME]; export function convertCompositeRespToGeoJson(esResponse, renderAs) { return convertToGeoJson( @@ -20,7 +20,7 @@ export function convertCompositeRespToGeoJson(esResponse, renderAs) { return _.get(esResponse, 'aggregations.compositeSplit.buckets', []); }, (gridBucket) => { - return gridBucket.key.gridSplit; + return gridBucket.key[GEOTILE_GRID_AGG_NAME]; } ); } @@ -30,7 +30,7 @@ export function convertRegularRespToGeoJson(esResponse, renderAs) { esResponse, renderAs, (esResponse) => { - return _.get(esResponse, 'aggregations.gridSplit.buckets', []); + return _.get(esResponse, `aggregations.${GEOTILE_GRID_AGG_NAME}.buckets`, []); }, (gridBucket) => { return gridBucket.key; @@ -49,7 +49,7 @@ function convertToGeoJson(esResponse, renderAs, pluckGridBuckets, pluckGridKey) type: 'Feature', geometry: rowToGeometry({ gridKey, - gridCentroid: gridBucket.gridCentroid, + [GEOCENTROID_AGG_NAME]: gridBucket[GEOCENTROID_AGG_NAME], renderAs, }), id: gridKey, diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.test.ts b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.test.ts similarity index 97% rename from x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.test.ts rename to x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.test.ts index 523cc86915010..ee40a1f2fc751 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.test.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.test.ts @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../kibana_services', () => {}); - // @ts-ignore import { convertCompositeRespToGeoJson, convertRegularRespToGeoJson } from './convert_to_geojson'; -import { RENDER_AS } from '../../../../common/constants'; +import { RENDER_AS } from '../constants'; describe('convertCompositeRespToGeoJson', () => { const esResponse = { diff --git a/x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts similarity index 89% rename from x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts rename to x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts index e57efca94d95e..cff8ba119e1de 100644 --- a/x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts @@ -5,8 +5,8 @@ */ import { FeatureCollection, GeoJsonProperties } from 'geojson'; -import { MapExtent } from './descriptor_types'; -import { ES_GEO_FIELD_TYPE } from './constants'; +import { MapExtent } from '../descriptor_types'; +import { ES_GEO_FIELD_TYPE } from '../constants'; export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent; diff --git a/x-pack/plugins/maps/common/elasticsearch_geo_utils.js b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.js similarity index 98% rename from x-pack/plugins/maps/common/elasticsearch_geo_utils.js rename to x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.js index f2bf83ae18bb0..be214e3b01e67 100644 --- a/x-pack/plugins/maps/common/elasticsearch_geo_utils.js +++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.js @@ -15,9 +15,9 @@ import { POLYGON_COORDINATES_EXTERIOR_INDEX, LON_INDEX, LAT_INDEX, -} from '../common/constants'; -import { getEsSpatialRelationLabel } from './i18n_getters'; -import { FILTERS } from '../../../../src/plugins/data/common'; +} from '../constants'; +import { getEsSpatialRelationLabel } from '../i18n_getters'; +import { FILTERS } from '../../../../../src/plugins/data/common'; import turfCircle from '@turf/circle'; const SPATIAL_FILTER_TYPE = FILTERS.SPATIAL_FILTER; diff --git a/x-pack/plugins/maps/common/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js similarity index 100% rename from x-pack/plugins/maps/common/elasticsearch_geo_utils.test.js rename to x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js diff --git a/x-pack/plugins/maps/public/classes/util/es_agg_utils.test.ts b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.test.ts similarity index 100% rename from x-pack/plugins/maps/public/classes/util/es_agg_utils.test.ts rename to x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.test.ts diff --git a/x-pack/plugins/maps/public/classes/util/es_agg_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts similarity index 92% rename from x-pack/plugins/maps/public/classes/util/es_agg_utils.ts rename to x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts index 329a2a6fc64fb..7828c3cc6410b 100644 --- a/x-pack/plugins/maps/public/classes/util/es_agg_utils.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts @@ -5,8 +5,8 @@ */ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { IndexPattern, IFieldType } from '../../../../../../src/plugins/data/public'; -import { TOP_TERM_PERCENTAGE_SUFFIX } from '../../../common/constants'; +import { IndexPattern, IFieldType } from '../../../../../src/plugins/data/common'; +import { TOP_TERM_PERCENTAGE_SUFFIX } from '../constants'; export function getField(indexPattern: IndexPattern, fieldName: string) { const field = indexPattern.fields.getByName(fieldName); diff --git a/x-pack/plugins/maps/common/elasticsearch_util/index.ts b/x-pack/plugins/maps/common/elasticsearch_util/index.ts new file mode 100644 index 0000000000000..ffb4a542374fa --- /dev/null +++ b/x-pack/plugins/maps/common/elasticsearch_util/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; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './es_agg_utils'; +export * from './convert_to_geojson'; +export * from './elasticsearch_geo_utils'; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.test.js b/x-pack/plugins/maps/common/geo_tile_utils.test.js similarity index 96% rename from x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.test.js rename to x-pack/plugins/maps/common/geo_tile_utils.test.js index 88a6ce048a178..ae2623e168766 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.test.js +++ b/x-pack/plugins/maps/common/geo_tile_utils.test.js @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../kibana_services', () => {}); - import { parseTileKey, getTileBoundingBox, expandToTileBoundaries } from './geo_tile_utils'; it('Should parse tile key', () => { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.js b/x-pack/plugins/maps/common/geo_tile_utils.ts similarity index 65% rename from x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.js rename to x-pack/plugins/maps/common/geo_tile_utils.ts index 89b24522e4275..c6e35224458c5 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.js +++ b/x-pack/plugins/maps/common/geo_tile_utils.ts @@ -5,18 +5,32 @@ */ import _ from 'lodash'; -import { DECIMAL_DEGREES_PRECISION } from '../../../../common/constants'; -import { clampToLatBounds } from '../../../../common/elasticsearch_geo_utils'; +import { DECIMAL_DEGREES_PRECISION } from './constants'; +import { clampToLatBounds } from './elasticsearch_util'; +import { MapExtent } from './descriptor_types'; const ZOOM_TILE_KEY_INDEX = 0; const X_TILE_KEY_INDEX = 1; const Y_TILE_KEY_INDEX = 2; -function getTileCount(zoom) { +function getTileCount(zoom: number): number { return Math.pow(2, zoom); } -export function parseTileKey(tileKey) { +export interface ESBounds { + top_left: { + lon: number; + lat: number; + }; + bottom_right: { + lon: number; + lat: number; + }; +} + +export function parseTileKey( + tileKey: string +): { x: number; y: number; zoom: number; tileCount: number } { const tileKeyParts = tileKey.split('/'); if (tileKeyParts.length !== 3) { @@ -42,7 +56,7 @@ export function parseTileKey(tileKey) { return { x, y, zoom, tileCount }; } -function sinh(x) { +function sinh(x: number): number { return (Math.exp(x) - Math.exp(-x)) / 2; } @@ -55,24 +69,52 @@ function sinh(x) { // We add one extra decimal level of precision because, at high zoom // levels rounding exactly can cause the boxes to render as uneven sizes // (some will be slightly larger and some slightly smaller) -function precisionRounding(v, minPrecision, binSize) { +function precisionRounding(v: number, minPrecision: number, binSize: number): number { let precision = Math.ceil(Math.abs(Math.log10(binSize))) + 1; precision = Math.max(precision, minPrecision); return _.round(v, precision); } -function tileToLatitude(y, tileCount) { +export function tile2long(x: number, z: number): number { + const tileCount = getTileCount(z); + return tileToLongitude(x, tileCount); +} + +export function tile2lat(y: number, z: number): number { + const tileCount = getTileCount(z); + return tileToLatitude(y, tileCount); +} + +export function tileToESBbox(x: number, y: number, z: number): ESBounds { + const wLon = tile2long(x, z); + const sLat = tile2lat(y + 1, z); + const eLon = tile2long(x + 1, z); + const nLat = tile2lat(y, z); + + return { + top_left: { + lon: wLon, + lat: nLat, + }, + bottom_right: { + lon: eLon, + lat: sLat, + }, + }; +} + +export function tileToLatitude(y: number, tileCount: number) { const radians = Math.atan(sinh(Math.PI - (2 * Math.PI * y) / tileCount)); const lat = (180 / Math.PI) * radians; return precisionRounding(lat, DECIMAL_DEGREES_PRECISION, 180 / tileCount); } -function tileToLongitude(x, tileCount) { +export function tileToLongitude(x: number, tileCount: number) { const lon = (x / tileCount) * 360 - 180; return precisionRounding(lon, DECIMAL_DEGREES_PRECISION, 360 / tileCount); } -export function getTileBoundingBox(tileKey) { +export function getTileBoundingBox(tileKey: string) { const { x, y, tileCount } = parseTileKey(tileKey); return { @@ -83,22 +125,22 @@ export function getTileBoundingBox(tileKey) { }; } -function sec(value) { +function sec(value: number): number { return 1 / Math.cos(value); } -function latitudeToTile(lat, tileCount) { +function latitudeToTile(lat: number, tileCount: number) { const radians = (clampToLatBounds(lat) * Math.PI) / 180; const y = ((1 - Math.log(Math.tan(radians) + sec(radians)) / Math.PI) / 2) * tileCount; return Math.floor(y); } -function longitudeToTile(lon, tileCount) { +function longitudeToTile(lon: number, tileCount: number) { const x = ((lon + 180) / 360) * tileCount; return Math.floor(x); } -export function expandToTileBoundaries(extent, zoom) { +export function expandToTileBoundaries(extent: MapExtent, zoom: number): MapExtent { const tileCount = getTileCount(zoom); const upperLeftX = longitudeToTile(extent.minLon, tileCount); diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts index 2876f3d668a69..14d8196900506 100644 --- a/x-pack/plugins/maps/public/actions/data_request_actions.ts +++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts @@ -40,7 +40,7 @@ import { ILayer } from '../classes/layers/layer'; import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer'; import { DataMeta, MapExtent, MapFilters } from '../../common/descriptor_types'; import { DataRequestAbortError } from '../classes/util/data_request'; -import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_geo_utils'; +import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_util'; import { IVectorStyle } from '../classes/styles/vector/vector_style'; const FIT_TO_BOUNDS_SCALE_FACTOR = 0.1; diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index b00594cb7fb23..09491e5c3a7b3 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -54,7 +54,7 @@ import { MapRefreshConfig, } from '../../common/descriptor_types'; import { INITIAL_LOCATION } from '../../common/constants'; -import { scaleBounds } from '../../common/elasticsearch_geo_utils'; +import { scaleBounds } from '../../common/elasticsearch_util'; export function setMapInitError(errorMessage: string) { return { diff --git a/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts b/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts index 7b184819b839b..8cff98205186f 100644 --- a/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts @@ -12,7 +12,7 @@ import { IVectorSource } from '../sources/vector_source'; import { ESDocField } from './es_doc_field'; import { AGG_TYPE, FIELD_ORIGIN } from '../../../common/constants'; import { isMetricCountable } from '../util/is_metric_countable'; -import { getField, addFieldToDSL } from '../util/es_agg_utils'; +import { getField, addFieldToDSL } from '../../../common/elasticsearch_util'; import { TopTermPercentageField } from './top_term_percentage_field'; import { ITooltipProperty, TooltipProperty } from '../tooltips/tooltip_property'; import { ESAggTooltipProperty } from '../tooltips/es_agg_tooltip_property'; @@ -30,6 +30,7 @@ export class ESAggField implements IESAggField { private readonly _label?: string; private readonly _aggType: AGG_TYPE; private readonly _esDocField?: IField | undefined; + private readonly _canReadFromGeoJson: boolean; constructor({ label, @@ -37,18 +38,21 @@ export class ESAggField implements IESAggField { aggType, esDocField, origin, + canReadFromGeoJson = true, }: { label?: string; source: IESAggSource; aggType: AGG_TYPE; esDocField?: IField; origin: FIELD_ORIGIN; + canReadFromGeoJson?: boolean; }) { this._source = source; this._origin = origin; this._label = label; this._aggType = aggType; this._esDocField = esDocField; + this._canReadFromGeoJson = canReadFromGeoJson; } getSource(): IVectorSource { @@ -132,18 +136,19 @@ export class ESAggField implements IESAggField { } supportsAutoDomain(): boolean { - return true; + return this._canReadFromGeoJson ? true : this.supportsFieldMeta(); } canReadFromGeoJson(): boolean { - return true; + return this._canReadFromGeoJson; } } export function esAggFieldsFactory( aggDescriptor: AggDescriptor, source: IESAggSource, - origin: FIELD_ORIGIN + origin: FIELD_ORIGIN, + canReadFromGeoJson: boolean = true ): IESAggField[] { const aggField = new ESAggField({ label: aggDescriptor.label, @@ -153,12 +158,13 @@ export function esAggFieldsFactory( aggType: aggDescriptor.type, source, origin, + canReadFromGeoJson, }); const aggFields: IESAggField[] = [aggField]; if (aggDescriptor.field && aggDescriptor.type === AGG_TYPE.TERMS) { - aggFields.push(new TopTermPercentageField(aggField)); + aggFields.push(new TopTermPercentageField(aggField, canReadFromGeoJson)); } return aggFields; diff --git a/x-pack/plugins/maps/public/classes/fields/field.ts b/x-pack/plugins/maps/public/classes/fields/field.ts index 2c190d54f0265..658c2bba87847 100644 --- a/x-pack/plugins/maps/public/classes/fields/field.ts +++ b/x-pack/plugins/maps/public/classes/fields/field.ts @@ -21,12 +21,12 @@ export interface IField { getOrdinalFieldMetaRequest(): Promise; getCategoricalFieldMetaRequest(size: number): Promise; - // Determines whether Maps-app can automatically determine the domain of the field-values + // Whether Maps-app can automatically determine the domain of the field-values // if this is not the case (e.g. for .mvt tiled data), // then styling properties that require the domain to be known cannot use this property. supportsAutoDomain(): boolean; - // Determinse wheter Maps-app can automatically deterime the domain of the field-values + // Whether Maps-app can automatically determine the domain of the field-values // _without_ having to retrieve the data as GeoJson // e.g. for ES-sources, this would use the extended_stats API supportsFieldMeta(): boolean; diff --git a/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts b/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts index fc931b13619ef..50db04d08b2aa 100644 --- a/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts @@ -6,16 +6,17 @@ import { IESAggField } from './es_agg_field'; import { IVectorSource } from '../sources/vector_source'; -// @ts-ignore import { ITooltipProperty, TooltipProperty } from '../tooltips/tooltip_property'; import { TOP_TERM_PERCENTAGE_SUFFIX } from '../../../common/constants'; import { FIELD_ORIGIN } from '../../../common/constants'; export class TopTermPercentageField implements IESAggField { private readonly _topTermAggField: IESAggField; + private readonly _canReadFromGeoJson: boolean; - constructor(topTermAggField: IESAggField) { + constructor(topTermAggField: IESAggField, canReadFromGeoJson: boolean = true) { this._topTermAggField = topTermAggField; + this._canReadFromGeoJson = canReadFromGeoJson; } getSource(): IVectorSource { @@ -61,7 +62,7 @@ export class TopTermPercentageField implements IESAggField { } supportsAutoDomain(): boolean { - return true; + return this._canReadFromGeoJson; } supportsFieldMeta(): boolean { @@ -81,6 +82,6 @@ export class TopTermPercentageField implements IESAggField { } canReadFromGeoJson(): boolean { - return true; + return this._canReadFromGeoJson; } } diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index 8026f48fe6093..cd720063c6703 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -423,7 +423,7 @@ export class AbstractLayer implements ILayer { renderSourceSettingsEditor({ onChange }: SourceEditorArgs) { const source = this.getSourceForEditing(); - return source.renderSourceSettingsEditor({ onChange }); + return source.renderSourceSettingsEditor({ onChange, currentLayerType: this._descriptor.type }); } getPrevRequestToken(dataId: string): symbol | undefined { diff --git a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts index a9c886617d3af..be947d79f4e39 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts @@ -31,14 +31,25 @@ export interface IESAggSource extends IESSource { export class AbstractESAggSource extends AbstractESSource { private readonly _metricFields: IESAggField[]; + private readonly _canReadFromGeoJson: boolean; - constructor(descriptor: AbstractESAggSourceDescriptor, inspectorAdapters: Adapters) { + constructor( + descriptor: AbstractESAggSourceDescriptor, + inspectorAdapters: Adapters, + canReadFromGeoJson = true + ) { super(descriptor, inspectorAdapters); this._metricFields = []; + this._canReadFromGeoJson = canReadFromGeoJson; if (descriptor.metrics) { descriptor.metrics.forEach((aggDescriptor: AggDescriptor) => { this._metricFields.push( - ...esAggFieldsFactory(aggDescriptor, this, this.getOriginForField()) + ...esAggFieldsFactory( + aggDescriptor, + this, + this.getOriginForField(), + this._canReadFromGeoJson + ) ); }); } @@ -72,7 +83,12 @@ export class AbstractESAggSource extends AbstractESSource { const metrics = this._metricFields.filter((esAggField) => esAggField.isValid()); // Handle case where metrics is empty because older saved object state is empty array or there are no valid aggs. return metrics.length === 0 - ? esAggFieldsFactory({ type: AGG_TYPE.COUNT }, this, this.getOriginForField()) + ? esAggFieldsFactory( + { type: AGG_TYPE.COUNT }, + this, + this.getOriginForField(), + this._canReadFromGeoJson + ) : metrics; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/resolution_editor.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/resolution_editor.test.tsx.snap new file mode 100644 index 0000000000000..ca9775594a9d7 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/resolution_editor.test.tsx.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`resolution editor should add super-fine option 1`] = ` + + + +`; + +exports[`resolution editor should omit super-fine option 1`] = ` + + + +`; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/update_source_editor.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/update_source_editor.test.tsx.snap new file mode 100644 index 0000000000000..dfce6b36396a7 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/update_source_editor.test.tsx.snap @@ -0,0 +1,121 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`source editor geo_grid_source default vector layer config should allow super-fine option 1`] = ` + + + +
+ +
+
+ + +
+ + + +
+ +
+
+ + + +
+ +
+`; + +exports[`source editor geo_grid_source should put limitations based on heatmap-rendering selection should not allow super-fine option for heatmaps and should not allow multiple metrics 1`] = ` + + + +
+ +
+
+ + +
+ + + +
+ +
+
+ + + +
+ +
+`; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts index 2ce4353fca13c..ada76b8e4e674 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts @@ -5,11 +5,17 @@ */ import { AbstractESAggSource } from '../es_agg_source'; -import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types'; +import { + ESGeoGridSourceDescriptor, + MapFilters, + MapQuery, + VectorSourceSyncMeta, +} from '../../../../common/descriptor_types'; import { GRID_RESOLUTION } from '../../../../common/constants'; import { IField } from '../../fields/field'; +import { ITiledSingleLayerVectorSource } from '../vector_source'; -export class ESGeoGridSource extends AbstractESAggSource { +export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingleLayerVectorSource { static createDescriptor({ indexPatternId, geoField, @@ -19,8 +25,27 @@ export class ESGeoGridSource extends AbstractESAggSource { constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown); + readonly _descriptor: ESGeoGridSourceDescriptor; + getFieldNames(): string[]; getGridResolution(): GRID_RESOLUTION; getGeoGridPrecision(zoom: number): number; createField({ fieldName }: { fieldName: string }): IField; + + getLayerName(): string; + + getUrlTemplateWithMeta( + searchFilters: MapFilters & { + applyGlobalQuery: boolean; + fieldNames: string[]; + geogridPrecision?: number; + sourceQuery: MapQuery; + sourceMeta: VectorSourceSyncMeta; + } + ): Promise<{ + layerName: string; + urlTemplate: string; + minSourceZoom: number; + maxSourceZoom: number; + }>; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js index aa167cb577672..89258f04612fd 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js @@ -7,7 +7,11 @@ import React from 'react'; import uuid from 'uuid/v4'; -import { convertCompositeRespToGeoJson, convertRegularRespToGeoJson } from './convert_to_geojson'; +import { + convertCompositeRespToGeoJson, + convertRegularRespToGeoJson, + makeESBbox, +} from '../../../../common/elasticsearch_util'; import { UpdateSourceEditor } from './update_source_editor'; import { SOURCE_TYPES, @@ -15,13 +19,20 @@ import { RENDER_AS, GRID_RESOLUTION, VECTOR_SHAPE_TYPE, + MVT_SOURCE_LAYER_NAME, + GIS_API_PATH, + MVT_GETGRIDTILE_API_PATH, + GEOTILE_GRID_AGG_NAME, + GEOCENTROID_AGG_NAME, } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source'; import { DataRequestAbortError } from '../../util/data_request'; import { registerSource } from '../source_registry'; -import { makeESBbox } from '../../../../common/elasticsearch_geo_utils'; + +import rison from 'rison-node'; +import { getHttp } from '../../../kibana_services'; export const MAX_GEOTILE_LEVEL = 29; @@ -48,9 +59,14 @@ export class ESGeoGridSource extends AbstractESAggSource { }; } - renderSourceSettingsEditor({ onChange }) { + constructor(descriptor, inspectorAdapters) { + super(descriptor, inspectorAdapters, descriptor.resolution !== GRID_RESOLUTION.SUPER_FINE); + } + + renderSourceSettingsEditor({ onChange, currentLayerType }) { return ( { @@ -96,59 +99,67 @@ describe('ESGeoGridSource', () => { }; }; - describe('getGeoJsonWithMeta', () => { - let mockSearchSource: unknown; - beforeEach(async () => { - mockSearchSource = new MockSearchSource(); - const mockSearchService = { - searchSource: { - async create() { - return mockSearchSource as SearchSource; - }, - createEmpty() { - return mockSearchSource as SearchSource; - }, + let mockSearchSource: unknown; + beforeEach(async () => { + mockSearchSource = new MockSearchSource(); + const mockSearchService = { + searchSource: { + async create() { + return mockSearchSource as SearchSource; }, - }; + createEmpty() { + return mockSearchSource as SearchSource; + }, + }, + }; - // @ts-expect-error - getIndexPatternService.mockReturnValue(mockIndexPatternService); - // @ts-expect-error - getSearchService.mockReturnValue(mockSearchService); + // @ts-expect-error + getIndexPatternService.mockReturnValue(mockIndexPatternService); + // @ts-expect-error + getSearchService.mockReturnValue(mockSearchService); + // @ts-expect-error + getHttp.mockReturnValue({ + basePath: { + prepend(path: string) { + return `rootdir${path};`; + }, + }, }); + }); - const extent: MapExtent = { - minLon: -160, - minLat: -80, - maxLon: 160, - maxLat: 80, - }; + const extent: MapExtent = { + minLon: -160, + minLat: -80, + maxLon: 160, + maxLat: 80, + }; - const mapFilters: VectorSourceRequestMeta = { - geogridPrecision: 4, - filters: [], - timeFilters: { - from: 'now', - to: '15m', - mode: 'relative', - }, - extent, - applyGlobalQuery: true, - fieldNames: [], - buffer: extent, - sourceQuery: { - query: '', - language: 'KQL', - queryLastTriggeredAt: '2019-04-25T20:53:22.331Z', - }, - sourceMeta: null, - zoom: 0, - }; + const vectorSourceRequestMeta: VectorSourceRequestMeta = { + geogridPrecision: 4, + filters: [], + timeFilters: { + from: 'now', + to: '15m', + mode: 'relative', + }, + extent, + applyGlobalQuery: true, + fieldNames: [], + buffer: extent, + sourceQuery: { + query: '', + language: 'KQL', + queryLastTriggeredAt: '2019-04-25T20:53:22.331Z', + }, + sourceMeta: null, + zoom: 0, + }; + describe('getGeoJsonWithMeta', () => { it('Should configure the SearchSource correctly', async () => { const { data, meta } = await geogridSource.getGeoJsonWithMeta( 'foobarLayer', - mapFilters, + vectorSourceRequestMeta, () => {} ); @@ -215,5 +226,48 @@ describe('ESGeoGridSource', () => { it('should use heuristic to derive precision', () => { expect(geogridSource.getGeoGridPrecision(10)).toBe(12); }); + + it('Should not return valid precision for super-fine resolution', () => { + const superFineSource = new ESGeoGridSource( + { + id: 'foobar', + indexPatternId: 'fooIp', + geoField: geoFieldName, + metrics: [], + resolution: GRID_RESOLUTION.SUPER_FINE, + type: SOURCE_TYPES.ES_GEO_GRID, + requestType: RENDER_AS.HEATMAP, + }, + {} + ); + expect(superFineSource.getGeoGridPrecision(10)).toBe(NaN); + }); + }); + + describe('ITiledSingleLayerVectorSource', () => { + it('getLayerName', () => { + expect(geogridSource.getLayerName()).toBe('source_layer'); + }); + + it('getMinZoom', () => { + expect(geogridSource.getMinZoom()).toBe(0); + }); + + it('getMaxZoom', () => { + expect(geogridSource.getMaxZoom()).toBe(24); + }); + + it('getUrlTemplateWithMeta', async () => { + const urlTemplateWithMeta = await geogridSource.getUrlTemplateWithMeta( + vectorSourceRequestMeta + ); + + expect(urlTemplateWithMeta.layerName).toBe('source_layer'); + expect(urlTemplateWithMeta.minSourceZoom).toBe(0); + expect(urlTemplateWithMeta.maxSourceZoom).toBe(24); + expect(urlTemplateWithMeta.urlTemplate).toBe( + "rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628)))))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point" + ); + }); }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.js index 28c24f58a0efc..71133cb25280c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.js @@ -9,7 +9,7 @@ import { GRID_RESOLUTION } from '../../../../common/constants'; import { EuiSelect, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -const OPTIONS = [ +const BASE_OPTIONS = [ { value: GRID_RESOLUTION.COARSE, text: i18n.translate('xpack.maps.source.esGrid.coarseDropdownOption', { @@ -30,7 +30,18 @@ const OPTIONS = [ }, ]; -export function ResolutionEditor({ resolution, onChange }) { +export function ResolutionEditor({ resolution, onChange, includeSuperFine }) { + const options = [...BASE_OPTIONS]; + + if (includeSuperFine) { + options.push({ + value: GRID_RESOLUTION.SUPER_FINE, + text: i18n.translate('xpack.maps.source.esGrid.superFineDropDownOption', { + defaultMessage: 'super fine (beta)', + }), + }); + } + return ( onChange(e.target.value)} compressed diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.test.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.test.tsx new file mode 100644 index 0000000000000..369203dbe16c0 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.test.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +// @ts-expect-error +import { ResolutionEditor } from './resolution_editor'; +import { GRID_RESOLUTION } from '../../../../common/constants'; + +const defaultProps = { + resolution: GRID_RESOLUTION.COARSE, + onChange: () => {}, + includeSuperFine: false, +}; + +describe('resolution editor', () => { + test('should omit super-fine option', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + test('should add super-fine option', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.js index ac7d809c40f61..7e885c291b952 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.js @@ -6,7 +6,7 @@ import React, { Fragment, Component } from 'react'; -import { RENDER_AS } from '../../../../common/constants'; +import { GRID_RESOLUTION, LAYER_TYPE } from '../../../../common/constants'; import { MetricsEditor } from '../../../components/metrics_editor'; import { getIndexPatternService } from '../../../kibana_services'; import { ResolutionEditor } from './resolution_editor'; @@ -62,8 +62,25 @@ export class UpdateSourceEditor extends Component { this.props.onChange({ propName: 'metrics', value: metrics }); }; - _onResolutionChange = (e) => { - this.props.onChange({ propName: 'resolution', value: e }); + _onResolutionChange = (resolution) => { + let newLayerType; + if ( + this.props.currentLayerType === LAYER_TYPE.VECTOR || + this.props.currentLayerType === LAYER_TYPE.TILED_VECTOR + ) { + newLayerType = + resolution === GRID_RESOLUTION.SUPER_FINE ? LAYER_TYPE.TILED_VECTOR : LAYER_TYPE.VECTOR; + } else if (this.props.currentLayerType === LAYER_TYPE.HEATMAP) { + if (resolution === GRID_RESOLUTION.SUPER_FINE) { + throw new Error('Heatmap does not support SUPER_FINE resolution'); + } else { + newLayerType = LAYER_TYPE.HEATMAP; + } + } else { + throw new Error('Unexpected layer-type'); + } + + this.props.onChange({ propName: 'resolution', value: resolution, newLayerType }); }; _onRequestTypeSelect = (requestType) => { @@ -72,13 +89,13 @@ export class UpdateSourceEditor extends Component { _renderMetricsPanel() { const metricsFilter = - this.props.renderAs === RENDER_AS.HEATMAP + this.props.currentLayerType === LAYER_TYPE.HEATMAP ? (metric) => { //these are countable metrics, where blending heatmap color blobs make sense return isMetricCountable(metric.value); } : null; - const allowMultipleMetrics = this.props.renderAs !== RENDER_AS.HEATMAP; + const allowMultipleMetrics = this.props.currentLayerType !== LAYER_TYPE.HEATMAP; return ( @@ -115,6 +132,7 @@ export class UpdateSourceEditor extends Component { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.test.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.test.tsx new file mode 100644 index 0000000000000..ceb79230bc832 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +// @ts-expect-error +import { UpdateSourceEditor } from './update_source_editor'; +import { GRID_RESOLUTION, LAYER_TYPE, RENDER_AS } from '../../../../common/constants'; + +const defaultProps = { + currentLayerType: LAYER_TYPE.VECTOR, + indexPatternId: 'foobar', + onChange: () => {}, + metrics: [], + renderAs: RENDER_AS.POINT, + resolution: GRID_RESOLUTION.COARSE, +}; + +describe('source editor geo_grid_source', () => { + describe('default vector layer config', () => { + test('should allow super-fine option', async () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('should put limitations based on heatmap-rendering selection', () => { + test('should not allow super-fine option for heatmaps and should not allow multiple metrics', async () => { + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/convert_to_lines.js b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/convert_to_lines.js index 96a7f50cdf523..24ac6d31bc645 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/convert_to_lines.js +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/convert_to_lines.js @@ -5,7 +5,7 @@ */ import _ from 'lodash'; -import { extractPropertiesFromBucket } from '../../util/es_agg_utils'; +import { extractPropertiesFromBucket } from '../../../../common/elasticsearch_util'; const LAT_INDEX = 0; const LON_INDEX = 1; diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js index 9ec54335d4e78..0360208ef8370 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js @@ -16,7 +16,7 @@ import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { convertToLines } from './convert_to_lines'; import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source'; import { registerSource } from '../source_registry'; -import { turfBboxToBounds } from '../../../../common/elasticsearch_geo_utils'; +import { turfBboxToBounds } from '../../../../common/elasticsearch_util'; import { DataRequestAbortError } from '../../util/data_request'; const MAX_GEOTILE_LEVEL = 29; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js index df83bd1cf5e60..edcafae54d54c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js @@ -10,7 +10,7 @@ import rison from 'rison-node'; import { AbstractESSource } from '../es_source'; import { getSearchService, getHttp } from '../../../kibana_services'; -import { hitsToGeoJson } from '../../../../common/elasticsearch_geo_utils'; +import { hitsToGeoJson, getField, addFieldToDSL } from '../../../../common/elasticsearch_util'; import { UpdateSourceEditor } from './update_source_editor'; import { SOURCE_TYPES, @@ -31,7 +31,7 @@ import uuid from 'uuid/v4'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; import { ESDocField } from '../../fields/es_doc_field'; -import { getField, addFieldToDSL } from '../../util/es_agg_utils'; + import { registerSource } from '../source_registry'; export const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', { diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js index d51ca46fd98ff..ab56ceeab4e77 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js @@ -11,14 +11,14 @@ import { getTimeFilter, getSearchService, } from '../../../kibana_services'; -import { createExtentFilter } from '../../../../common/elasticsearch_geo_utils'; +import { createExtentFilter } from '../../../../common/elasticsearch_util'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; import { copyPersistentState } from '../../../reducers/util'; import { DataRequestAbortError } from '../../util/data_request'; -import { expandToTileBoundaries } from '../es_geo_grid_source/geo_tile_utils'; +import { expandToTileBoundaries } from '../../../../common/geo_tile_utils'; import { search } from '../../../../../../../src/plugins/data/public'; export class AbstractESSource extends AbstractVectorSource { diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js index b4ad256c1598a..359d22d2c44ce 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js @@ -16,7 +16,11 @@ import { import { getJoinAggKey } from '../../../../common/get_agg_key'; import { ESDocField } from '../../fields/es_doc_field'; import { AbstractESAggSource } from '../es_agg_source'; -import { getField, addFieldToDSL, extractPropertiesFromBucket } from '../../util/es_agg_utils'; +import { + getField, + addFieldToDSL, + extractPropertiesFromBucket, +} from '../../../../common/elasticsearch_util'; const TERMS_AGG_NAME = 'join'; diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 7e7a7bd8f049d..946381817b8fc 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -18,6 +18,7 @@ import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view' export type SourceEditorArgs = { onChange: (...args: OnSourceChangeArgs[]) => void; + currentLayerType?: string; }; export type ImmutableSourceProperty = { @@ -50,7 +51,7 @@ export interface ISource { getImmutableProperties(): Promise; getAttributions(): Promise; isESSource(): boolean; - renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null; + renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null; supportsFitToBounds(): Promise; showJoinEditor(): boolean; getJoinsDisabledReason(): string | null; @@ -126,7 +127,7 @@ export class AbstractSource implements ISource { return []; } - renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null { + renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null { return null; } diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx index a300225178526..c75698805225f 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx @@ -86,6 +86,7 @@ export class HeatmapStyle implements IStyle { } else if (resolution === GRID_RESOLUTION.MOST_FINE) { radius = 32; } else { + // SUPER_FINE or any other is not supported. const errorMessage = i18n.translate('xpack.maps.style.heatmap.resolutionStyleErrorMessage', { defaultMessage: `Resolution param not recognized: {resolution}`, values: { resolution }, diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts b/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts index d190a62e6f300..49d6ccdeb9316 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { MB_LOOKUP_FUNCTION, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; +import { MB_LOOKUP_FUNCTION, VECTOR_SHAPE_TYPE, VECTOR_STYLES } from '../../../../common/constants'; import { Category } from '../../../../common/descriptor_types'; export function getOtherCategoryLabel() { @@ -14,8 +14,8 @@ export function getOtherCategoryLabel() { }); } -export function getComputedFieldName(styleName: string, fieldName: string) { - return `${getComputedFieldNamePrefix(fieldName)}__${styleName}`; +export function getComputedFieldName(styleName: VECTOR_STYLES, fieldName: string) { + return `${getComputedFieldNamePrefix(fieldName)}__${styleName as string}`; } export function getComputedFieldNamePrefix(fieldName: string) { diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js index 98267965fd30f..33a0f1c5bf088 100644 --- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js +++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js @@ -8,7 +8,7 @@ import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { URL_MAX_LENGTH } from '../../../../../../../src/core/public'; -import { createSpatialFilterWithGeometry } from '../../../../common/elasticsearch_geo_utils'; +import { createSpatialFilterWithGeometry } from '../../../../common/elasticsearch_util'; import { GEO_JSON_TYPE } from '../../../../common/constants'; import { GeometryFilterForm } from '../../../components/geometry_filter_form'; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js index 49675ac6a3924..0356a8267c18a 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js @@ -15,7 +15,7 @@ import { createSpatialFilterWithGeometry, getBoundingBoxGeometry, roundCoordinates, -} from '../../../../../common/elasticsearch_geo_utils'; +} from '../../../../../common/elasticsearch_util'; import { DrawTooltip } from './draw_tooltip'; const DRAW_RECTANGLE = 'draw_rectangle'; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/map/mb/view.js index eede1edf40cc4..ddc48cfc9c329 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/view.js @@ -23,7 +23,7 @@ import sprites1 from '@elastic/maki/dist/sprite@1.png'; import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { DrawControl } from './draw_control'; import { TooltipControl } from './tooltip_control'; -import { clampToLatBounds, clampToLonBounds } from '../../../../common/elasticsearch_geo_utils'; +import { clampToLatBounds, clampToLonBounds } from '../../../../common/elasticsearch_util'; import { getInitialView } from './get_initial_view'; import { getPreserveDrawingBuffer } from '../../../kibana_services'; diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx b/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx index 47f41f2b76f3e..8a0eb8db4d7aa 100644 --- a/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx +++ b/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx @@ -67,17 +67,17 @@ export function getTopNavConfig({ savedMap.description = newDescription; savedMap.copyOnSave = newCopyOnSave; - let id; + let savedObjectId; try { savedMap.syncWithStore(); - id = await savedMap.save({ + savedObjectId = await savedMap.save({ confirmOverwrite: false, isTitleDuplicateConfirmed, onTitleDuplicate, }); // id not returned when save fails because of duplicate title check. // return and let user confirm duplicate title. - if (!id) { + if (!savedObjectId) { return {}; } } catch (err) { @@ -105,7 +105,7 @@ export function getTopNavConfig({ getCoreChrome().docTitle.change(savedMap.title); setBreadcrumbs(); - goToSpecifiedPath(`/map/${id}${window.location.hash}`); + goToSpecifiedPath(`/map/${savedObjectId}${window.location.hash}`); const newlyCreated = newCopyOnSave || isNewMap; if (newlyCreated && !returnToOrigin) { @@ -113,14 +113,14 @@ export function getTopNavConfig({ } else if (!!originatingApp && returnToOrigin) { if (newlyCreated && stateTransfer) { stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { - state: { id, type: MAP_SAVED_OBJECT_TYPE }, + state: { input: { savedObjectId }, type: MAP_SAVED_OBJECT_TYPE }, }); } else { getNavigateToApp()(originatingApp); } } - return { id }; + return { id: savedObjectId }; } if (hasSaveAndReturnConfig) { diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index 03e0f753812c9..db4371e9cd590 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -31,7 +31,7 @@ import { SPATIAL_FILTERS_LAYER_ID, } from '../../common/constants'; // @ts-ignore -import { extractFeaturesFromFilters } from '../../common/elasticsearch_geo_utils'; +import { extractFeaturesFromFilters } from '../../common/elasticsearch_util'; import { MapStoreState } from '../reducers/store'; import { DataRequestDescriptor, diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts index 8688bbe549f51..2af6413da039b 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -6,11 +6,12 @@ import _ from 'lodash'; import { + SavedObject, SavedObjectAttribute, SavedObjectAttributes, SavedObjectsClientContract, } from 'kibana/server'; -import { IFieldType, IIndexPattern } from 'src/plugins/data/public'; +import { IFieldType, IndexPatternAttributes } from 'src/plugins/data/public'; import { ES_GEO_FIELD_TYPE, LAYER_TYPE, @@ -64,7 +65,9 @@ function getUniqueLayerCounts(layerCountsList: ILayerTypeCount[], mapsCount: num }, {}); } -function getIndexPatternsWithGeoFieldCount(indexPatterns: IIndexPattern[]) { +function getIndexPatternsWithGeoFieldCount( + indexPatterns: Array> +) { const fieldLists = indexPatterns.map((indexPattern) => indexPattern.attributes && indexPattern.attributes.fields ? JSON.parse(indexPattern.attributes.fields) @@ -112,7 +115,7 @@ function getEMSLayerCount(layerLists: LayerDescriptor[][]): ILayerTypeCount[] { } function isFieldGeoShape( - indexPatterns: IIndexPattern[], + indexPatterns: Array>, indexPatternId: string, geoField: string | undefined ): boolean { @@ -120,9 +123,11 @@ function isFieldGeoShape( return false; } - const matchIndexPattern = indexPatterns.find((indexPattern: IIndexPattern) => { - return indexPattern.id === indexPatternId; - }); + const matchIndexPattern = indexPatterns.find( + (indexPattern: SavedObject) => { + return indexPattern.id === indexPatternId; + } + ); if (!matchIndexPattern) { return false; @@ -140,7 +145,10 @@ function isFieldGeoShape( return !!matchField && matchField.type === ES_GEO_FIELD_TYPE.GEO_SHAPE; } -function isGeoShapeAggLayer(indexPatterns: IIndexPattern[], layer: LayerDescriptor): boolean { +function isGeoShapeAggLayer( + indexPatterns: Array>, + layer: LayerDescriptor +): boolean { if (layer.sourceDescriptor === null) { return false; } @@ -176,7 +184,7 @@ function isGeoShapeAggLayer(indexPatterns: IIndexPattern[], layer: LayerDescript function getGeoShapeAggCount( layerLists: LayerDescriptor[][], - indexPatterns: IIndexPattern[] + indexPatterns: Array> ): number { const countsPerMap: number[] = layerLists.map((layerList: LayerDescriptor[]) => { const geoShapeAggLayers = layerList.filter((layerDescriptor) => { @@ -204,7 +212,7 @@ export function buildMapsTelemetry({ settings, }: { mapSavedObjects: MapSavedObject[]; - indexPatternSavedObjects: IIndexPattern[]; + indexPatternSavedObjects: Array>; settings: SavedObjectAttribute; }): SavedObjectAttributes { const layerLists: LayerDescriptor[][] = getLayerLists(mapSavedObjects); @@ -283,10 +291,12 @@ export async function getMapsTelemetry(config: MapsConfigType) { const savedObjectsClient = getInternalRepository(); // @ts-ignore const mapSavedObjects: MapSavedObject[] = await getMapSavedObjects(savedObjectsClient); - const indexPatternSavedObjects: IIndexPattern[] = (await getIndexPatternSavedObjects( + const indexPatternSavedObjects: Array> = (await getIndexPatternSavedObjects( // @ts-ignore savedObjectsClient - )) as IIndexPattern[]; + )) as Array>; const settings: SavedObjectAttribute = { showMapVisualizationTypes: config.showMapVisualizationTypes, }; diff --git a/x-pack/plugins/maps/server/mvt/__tests__/json/0_0_0_gridagg.json b/x-pack/plugins/maps/server/mvt/__tests__/json/0_0_0_gridagg.json new file mode 100644 index 0000000000000..0945dc57fa512 --- /dev/null +++ b/x-pack/plugins/maps/server/mvt/__tests__/json/0_0_0_gridagg.json @@ -0,0 +1 @@ +{"took":2,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":10000,"relation":"gte"},"max_score":null,"hits":[]},"aggregations":{"gridSplit":{"buckets":[{"key":"7/37/48","doc_count":42637,"avg_of_TOTAL_AV":{"value":5398920.390458991},"gridCentroid":{"location":{"lat":40.77936432658204,"lon":-73.96795676049909},"count":42637}}]}}} diff --git a/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0.pbf b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_docs.pbf similarity index 100% rename from x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0.pbf rename to x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_docs.pbf diff --git a/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_asgrid.pbf b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_asgrid.pbf new file mode 100644 index 0000000000000..f2289865b8022 Binary files /dev/null and b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_asgrid.pbf differ diff --git a/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_aspoint.pbf b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_aspoint.pbf new file mode 100644 index 0000000000000..54b0791ccd136 Binary files /dev/null and b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_aspoint.pbf differ diff --git a/x-pack/plugins/maps/server/mvt/__tests__/tile_searches.ts b/x-pack/plugins/maps/server/mvt/__tests__/tile_es_responses.ts similarity index 57% rename from x-pack/plugins/maps/server/mvt/__tests__/tile_searches.ts rename to x-pack/plugins/maps/server/mvt/__tests__/tile_es_responses.ts index 317d6434cf81e..9fbaba21e71d5 100644 --- a/x-pack/plugins/maps/server/mvt/__tests__/tile_searches.ts +++ b/x-pack/plugins/maps/server/mvt/__tests__/tile_es_responses.ts @@ -7,10 +7,6 @@ import * as path from 'path'; import * as fs from 'fs'; -const search000path = path.resolve(__dirname, './json/0_0_0_search.json'); -const search000raw = fs.readFileSync(search000path); -const search000json = JSON.parse((search000raw as unknown) as string); - export const TILE_SEARCHES = { '0.0.0': { countResponse: { @@ -22,7 +18,18 @@ export const TILE_SEARCHES = { failed: 0, }, }, - searchResponse: search000json, + searchResponse: loadJson('./json/0_0_0_search.json'), + }, +}; + +export const TILE_GRIDAGGS = { + '0.0.0': { + gridAggResponse: loadJson('./json/0_0_0_gridagg.json'), }, - '1.1.0': {}, }; + +function loadJson(filePath: string) { + const absolutePath = path.resolve(__dirname, filePath); + const rawContents = fs.readFileSync(absolutePath); + return JSON.parse((rawContents as unknown) as string); +} diff --git a/x-pack/plugins/maps/server/mvt/get_tile.test.ts b/x-pack/plugins/maps/server/mvt/get_tile.test.ts index b9c928d594539..76c1741ab2ad0 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.test.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.test.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getTile } from './get_tile'; -import { TILE_SEARCHES } from './__tests__/tile_searches'; +import { getGridTile, getTile } from './get_tile'; +import { TILE_GRIDAGGS, TILE_SEARCHES } from './__tests__/tile_es_responses'; import { Logger } from 'src/core/server'; import * as path from 'path'; import * as fs from 'fs'; +import { ES_GEO_FIELD_TYPE, RENDER_AS } from '../../common/constants'; describe('getTile', () => { const mockCallElasticsearch = jest.fn(); @@ -51,13 +52,84 @@ describe('getTile', () => { callElasticsearch: mockCallElasticsearch, }); - if (tile === null) { - throw new Error('Tile should be created'); - } + compareTiles('./__tests__/pbf/0_0_0_docs.pbf', tile); + }); +}); + +describe('getGridTile', () => { + const mockCallElasticsearch = jest.fn(); + + const geometryFieldName = 'geometry'; + + // For mock-purposes only. The ES-call response is mocked in 0_0_0_gridagg.json file + const requestBody = { + _source: { excludes: [] }, + aggs: { + gridSplit: { + aggs: { + // eslint-disable-next-line @typescript-eslint/naming-convention + avg_of_TOTAL_AV: { avg: { field: 'TOTAL_AV' } }, + gridCentroid: { geo_centroid: { field: geometryFieldName } }, + }, + geotile_grid: { + bounds: null, + field: geometryFieldName, + precision: null, + shard_size: 65535, + size: 65535, + }, + }, + }, + docvalue_fields: [], + query: { + bool: { + filter: [], + }, + }, + script_fields: {}, + size: 0, + stored_fields: ['*'], + }; - const expectedPath = path.resolve(__dirname, './__tests__/pbf/0_0_0.pbf'); - const expectedBin = fs.readFileSync(expectedPath, 'binary'); - const expectedTile = Buffer.from(expectedBin, 'binary'); - expect(expectedTile.equals(tile)).toBe(true); + beforeEach(() => { + mockCallElasticsearch.mockReset(); + mockCallElasticsearch.mockImplementation((type) => { + return TILE_GRIDAGGS['0.0.0'].gridAggResponse; + }); + }); + + const defaultParams = { + x: 0, + y: 0, + z: 0, + index: 'manhattan', + requestBody, + geometryFieldName, + logger: ({ + info: () => {}, + } as unknown) as Logger, + callElasticsearch: mockCallElasticsearch, + requestType: RENDER_AS.POINT, + geoFieldType: ES_GEO_FIELD_TYPE.GEO_POINT, + }; + + test('0.0.0 tile (clusters)', async () => { + const tile = await getGridTile(defaultParams); + compareTiles('./__tests__/pbf/0_0_0_grid_aspoint.pbf', tile); + }); + + test('0.0.0 tile (grids)', async () => { + const tile = await getGridTile({ ...defaultParams, requestType: RENDER_AS.GRID }); + compareTiles('./__tests__/pbf/0_0_0_grid_asgrid.pbf', tile); }); }); + +function compareTiles(expectedRelativePath: string, actualTile: Buffer | null) { + if (actualTile === null) { + throw new Error('Tile should be created'); + } + const expectedPath = path.resolve(__dirname, expectedRelativePath); + const expectedBin = fs.readFileSync(expectedPath, 'binary'); + const expectedTile = Buffer.from(expectedBin, 'binary'); + expect(expectedTile.equals(actualTile)).toBe(true); +} diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts index 9621f7f174a30..dd88be7f80c2e 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -13,22 +13,89 @@ import { Feature, FeatureCollection, Polygon } from 'geojson'; import { ES_GEO_FIELD_TYPE, FEATURE_ID_PROPERTY_NAME, + GEOTILE_GRID_AGG_NAME, KBN_TOO_MANY_FEATURES_PROPERTY, + MAX_ZOOM, MVT_SOURCE_LAYER_NAME, + RENDER_AS, + SUPER_FINE_ZOOM_DELTA, } from '../../common/constants'; -import { hitsToGeoJson } from '../../common/elasticsearch_geo_utils'; +import { hitsToGeoJson } from '../../common/elasticsearch_util'; import { flattenHit } from './util'; +import { convertRegularRespToGeoJson } from '../../common/elasticsearch_util'; +import { ESBounds, tile2lat, tile2long, tileToESBbox } from '../../common/geo_tile_utils'; -interface ESBounds { - top_left: { - lon: number; - lat: number; - }; - bottom_right: { - lon: number; - lat: number; - }; +export async function getGridTile({ + logger, + callElasticsearch, + index, + geometryFieldName, + x, + y, + z, + requestBody = {}, + requestType = RENDER_AS.POINT, + geoFieldType = ES_GEO_FIELD_TYPE.GEO_POINT, +}: { + x: number; + y: number; + z: number; + geometryFieldName: string; + index: string; + callElasticsearch: (type: string, ...args: any[]) => Promise; + logger: Logger; + requestBody: any; + requestType: RENDER_AS; + geoFieldType: ES_GEO_FIELD_TYPE; +}): Promise { + const esBbox: ESBounds = tileToESBbox(x, y, z); + try { + let bboxFilter; + if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT) { + bboxFilter = { + geo_bounding_box: { + [geometryFieldName]: esBbox, + }, + }; + } else if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_SHAPE) { + const geojsonPolygon = tileToGeoJsonPolygon(x, y, z); + bboxFilter = { + geo_shape: { + [geometryFieldName]: { + shape: geojsonPolygon, + relation: 'INTERSECTS', + }, + }, + }; + } else { + throw new Error(`${geoFieldType} is not valid geo field-type`); + } + requestBody.query.bool.filter.push(bboxFilter); + + requestBody.aggs[GEOTILE_GRID_AGG_NAME].geotile_grid.precision = Math.min( + z + SUPER_FINE_ZOOM_DELTA, + MAX_ZOOM + ); + requestBody.aggs[GEOTILE_GRID_AGG_NAME].geotile_grid.bounds = esBbox; + + const esGeotileGridQuery = { + index, + body: requestBody, + }; + + const gridAggResult = await callElasticsearch('search', esGeotileGridQuery); + const features: Feature[] = convertRegularRespToGeoJson(gridAggResult, requestType); + const featureCollection: FeatureCollection = { + features, + type: 'FeatureCollection', + }; + + return createMvtTile(featureCollection, z, x, y); + } catch (e) { + logger.warn(`Cannot generate grid-tile for ${z}/${x}/${y}: ${e.message}`); + return null; + } } export async function getTile({ @@ -149,26 +216,7 @@ export async function getTile({ type: 'FeatureCollection', }; - const tileIndex = geojsonvt(featureCollection, { - maxZoom: 24, // max zoom to preserve detail on; can't be higher than 24 - tolerance: 3, // simplification tolerance (higher means simpler) - extent: 4096, // tile extent (both width and height) - buffer: 64, // tile buffer on each side - debug: 0, // logging level (0 to disable, 1 or 2) - lineMetrics: false, // whether to enable line metrics tracking for LineString/MultiLineString features - promoteId: null, // name of a feature property to promote to feature.id. Cannot be used with `generateId` - generateId: false, // whether to generate feature ids. Cannot be used with `promoteId` - indexMaxZoom: 5, // max zoom in the initial tile index - indexMaxPoints: 100000, // max number of points per tile in the index - }); - const tile = tileIndex.getTile(z, x, y); - - if (tile) { - const pbf = vtpbf.fromGeojsonVt({ [MVT_SOURCE_LAYER_NAME]: tile }, { version: 2 }); - return Buffer.from(pbf); - } else { - return null; - } + return createMvtTile(featureCollection, z, x, y); } catch (e) { logger.warn(`Cannot generate tile for ${z}/${x}/${y}: ${e.message}`); return null; @@ -195,15 +243,6 @@ function tileToGeoJsonPolygon(x: number, y: number, z: number): Polygon { }; } -function tile2long(x: number, z: number): number { - return (x / Math.pow(2, z)) * 360 - 180; -} - -function tile2lat(y: number, z: number): number { - const n = Math.PI - (2 * Math.PI * y) / Math.pow(2, z); - return (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); -} - function esBboxToGeoJsonPolygon(esBounds: ESBounds): Polygon { let minLon = esBounds.top_left.lon; const maxLon = esBounds.bottom_right.lon; @@ -224,3 +263,31 @@ function esBboxToGeoJsonPolygon(esBounds: ESBounds): Polygon { ], }; } + +function createMvtTile( + featureCollection: FeatureCollection, + z: number, + x: number, + y: number +): Buffer | null { + const tileIndex = geojsonvt(featureCollection, { + maxZoom: 24, // max zoom to preserve detail on; can't be higher than 24 + tolerance: 3, // simplification tolerance (higher means simpler) + extent: 4096, // tile extent (both width and height) + buffer: 64, // tile buffer on each side + debug: 0, // logging level (0 to disable, 1 or 2) + lineMetrics: false, // whether to enable line metrics tracking for LineString/MultiLineString features + promoteId: null, // name of a feature property to promote to feature.id. Cannot be used with `generateId` + generateId: false, // whether to generate feature ids. Cannot be used with `promoteId` + indexMaxZoom: 5, // max zoom in the initial tile index + indexMaxPoints: 100000, // max number of points per tile in the index + }); + const tile = tileIndex.getTile(z, x, y); + + if (tile) { + const pbf = vtpbf.fromGeojsonVt({ [MVT_SOURCE_LAYER_NAME]: tile }, { version: 2 }); + return Buffer.from(pbf); + } else { + return null; + } +} diff --git a/x-pack/plugins/maps/server/mvt/mvt_routes.ts b/x-pack/plugins/maps/server/mvt/mvt_routes.ts index 32c14a355ba2a..266a240b53017 100644 --- a/x-pack/plugins/maps/server/mvt/mvt_routes.ts +++ b/x-pack/plugins/maps/server/mvt/mvt_routes.ts @@ -6,10 +6,21 @@ import rison from 'rison-node'; import { schema } from '@kbn/config-schema'; -import { Logger } from 'src/core/server'; +import { + KibanaRequest, + KibanaResponseFactory, + Logger, + RequestHandlerContext, +} from 'src/core/server'; import { IRouter } from 'src/core/server'; -import { MVT_GETTILE_API_PATH, API_ROOT_PATH } from '../../common/constants'; -import { getTile } from './get_tile'; +import { + MVT_GETTILE_API_PATH, + API_ROOT_PATH, + MVT_GETGRIDTILE_API_PATH, + ES_GEO_FIELD_TYPE, + RENDER_AS, +} from '../../common/constants'; +import { getGridTile, getTile } from './get_tile'; const CACHE_TIMEOUT = 0; // Todo. determine good value. Unsure about full-implications (e.g. wrt. time-based data). @@ -28,46 +39,93 @@ export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRou }), }, }, - async (context, request, response) => { + async ( + context: RequestHandlerContext, + request: KibanaRequest, unknown>, + response: KibanaResponseFactory + ) => { const { query } = request; + const requestBodyDSL = rison.decode(query.requestBody as string); - const callElasticsearch = async (type: string, ...args: any[]): Promise => { - return await context.core.elasticsearch.legacy.client.callAsCurrentUser(type, ...args); - }; + const tile = await getTile({ + logger, + callElasticsearch: makeCallElasticsearch(context), + geometryFieldName: query.geometryFieldName as string, + x: query.x as number, + y: query.y as number, + z: query.z as number, + index: query.index as string, + requestBody: requestBodyDSL as any, + }); - const requestBodyDSL = rison.decode(query.requestBody); + return sendResponse(response, tile); + } + ); - const tile = await getTile({ + router.get( + { + path: `${API_ROOT_PATH}/${MVT_GETGRIDTILE_API_PATH}`, + validate: { + query: schema.object({ + x: schema.number(), + y: schema.number(), + z: schema.number(), + geometryFieldName: schema.string(), + requestBody: schema.string(), + index: schema.string(), + requestType: schema.string(), + geoFieldType: schema.string(), + }), + }, + }, + async ( + context: RequestHandlerContext, + request: KibanaRequest, unknown>, + response: KibanaResponseFactory + ) => { + const { query } = request; + const requestBodyDSL = rison.decode(query.requestBody as string); + + const tile = await getGridTile({ logger, - callElasticsearch, - geometryFieldName: query.geometryFieldName, - x: query.x, - y: query.y, - z: query.z, - index: query.index, - requestBody: requestBodyDSL, + callElasticsearch: makeCallElasticsearch(context), + geometryFieldName: query.geometryFieldName as string, + x: query.x as number, + y: query.y as number, + z: query.z as number, + index: query.index as string, + requestBody: requestBodyDSL as any, + requestType: query.requestType as RENDER_AS, + geoFieldType: query.geoFieldType as ES_GEO_FIELD_TYPE, }); - if (tile) { - return response.ok({ - body: tile, - headers: { - 'content-disposition': 'inline', - 'content-length': `${tile.length}`, - 'Content-Type': 'application/x-protobuf', - 'Cache-Control': `max-age=${CACHE_TIMEOUT}`, - }, - }); - } else { - return response.ok({ - headers: { - 'content-disposition': 'inline', - 'content-length': '0', - 'Content-Type': 'application/x-protobuf', - 'Cache-Control': `max-age=${CACHE_TIMEOUT}`, - }, - }); - } + return sendResponse(response, tile); } ); } + +function sendResponse(response: KibanaResponseFactory, tile: any) { + const headers = { + 'content-disposition': 'inline', + 'content-length': tile ? `${tile.length}` : `0`, + 'Content-Type': 'application/x-protobuf', + 'Cache-Control': `max-age=${CACHE_TIMEOUT}`, + }; + + if (tile) { + return response.ok({ + body: tile, + headers, + }); + } else { + return response.ok({ + headers, + }); + } +} + +function makeCallElasticsearch(context: RequestHandlerContext) { + return async (type: string, ...args: any[]): Promise => { + return context.core.elasticsearch.legacy.client.callAsCurrentUser(type, ...args); + }; +} diff --git a/x-pack/plugins/maps_legacy_licensing/public/plugin.ts b/x-pack/plugins/maps_legacy_licensing/public/plugin.ts index eaf527f856bc5..ed7321b2c0e74 100644 --- a/x-pack/plugins/maps_legacy_licensing/public/plugin.ts +++ b/x-pack/plugins/maps_legacy_licensing/public/plugin.ts @@ -26,12 +26,10 @@ export type MapsLegacyLicensingStart = ReturnType; export class MapsLegacyLicensing implements Plugin { public setup(core: CoreSetup, plugins: MapsLegacyLicensingSetupDependencies) { - const { - licensing, - mapsLegacy: { serviceSettings }, - } = plugins; + const { licensing, mapsLegacy } = plugins; if (licensing) { - licensing.license$.subscribe((license: ILicense) => { + licensing.license$.subscribe(async (license: ILicense) => { + const serviceSettings = await mapsLegacy.getServiceSettings(); const { uid, isActive } = license; if (isActive && license.hasAtLeast('basic')) { serviceSettings.setQueryParams({ license: uid }); diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts index 9729240567c24..04a460251cb6f 100644 --- a/x-pack/plugins/ml/common/util/job_utils.ts +++ b/x-pack/plugins/ml/common/util/job_utils.ts @@ -4,9 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isEmpty from 'lodash/isEmpty'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isEqual from 'lodash/isEqual'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import pick from 'lodash/pick'; import semver from 'semver'; diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js index d5025fd3c3649..67db378fb7951 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js +++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js @@ -10,6 +10,8 @@ * getting the annotations via props (used in Anomaly Explorer and Single Series Viewer). */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import uniq from 'lodash/uniq'; import PropTypes from 'prop-types'; diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js index 378ee82805173..d6eb0afed753d 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js @@ -9,6 +9,8 @@ */ import PropTypes from 'prop-types'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; import React, { Component } from 'react'; diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js index 57f3a08713ffe..299173fc4436d 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js @@ -7,6 +7,8 @@ import { EuiButtonIcon, EuiLink, EuiScreenReaderOnly } from '@elastic/eui'; import React from 'react'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js index c4ef45800dc3d..e0b20ab731749 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js @@ -11,7 +11,11 @@ import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import pick from 'lodash/pick'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js b/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js index abdb0961351ab..505ccf46c5a62 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/influencers_cell.js @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js index 6025dd1c7433e..114dc463fa3a6 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import cloneDeep from 'lodash/cloneDeep'; import moment from 'moment'; import rison from 'rison-node'; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index 6aad5d53c3a3c..1949a3c339161 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -188,6 +188,40 @@ export const DataGrid: FC = memo( ); } + let errorCallout; + + if (status === INDEX_STATUS.ERROR) { + // if it's a searchBar syntax error leave the table visible so they can try again + if (errorMessage && !errorMessage.includes('failed to create query')) { + errorCallout = ( + +

{errorMessage}

+
+ ); + } else { + errorCallout = ( + + + {errorMessage} + + + ); + } + } + return (
{isWithHeader(props) && ( @@ -211,19 +245,9 @@ export const DataGrid: FC = memo( )} - {status === INDEX_STATUS.ERROR && ( + {errorCallout !== undefined && (
- - - {errorMessage} - - + {errorCallout}
)} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx index 84b1c4241aaf2..07a15b01fca93 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx @@ -6,15 +6,7 @@ import React, { Fragment, FC, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiPanel, - EuiSpacer, - EuiText, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; @@ -25,7 +17,6 @@ import { getToastNotifications } from '../../../../../util/dependency_cache'; import { DataFrameAnalyticsConfig, MAX_COLUMNS, - INDEX_STATUS, SEARCH_SIZE, defaultSearchQuery, getAnalysisType, @@ -95,43 +86,11 @@ export const ExplorationResultsTable: FC = React.memo( ); const docFieldsCount = classificationData.columnsWithCharts.length; - const { - columnsWithCharts, - errorMessage, - status, - tableItems, - visibleColumns, - } = classificationData; + const { columnsWithCharts, tableItems, visibleColumns } = classificationData; if (jobConfig === undefined || classificationData === undefined) { return null; } - // if it's a searchBar syntax error leave the table visible so they can try again - if (status === INDEX_STATUS.ERROR && !errorMessage.includes('failed to create query')) { - return ( - - - - - - {jobStatus !== undefined && ( - - {getTaskStateBadge(jobStatus)} - - )} - - -

{errorMessage}

-
-
- ); - } return ( = React.memo(({ jobId }) = const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); const outlierData = useOutlierData(indexPattern, jobConfig, searchQuery); - const { columnsWithCharts, errorMessage, status, tableItems } = outlierData; + const { columnsWithCharts, tableItems } = outlierData; const colorRange = useColorRange( COLOR_RANGE.BLUE, @@ -62,24 +55,6 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = jobConfig !== undefined ? getFeatureCount(jobConfig.dest.results_field, tableItems) : 1 ); - // if it's a searchBar syntax error leave the table visible so they can try again - if (status === INDEX_STATUS.ERROR && !errorMessage.includes('failed to create query')) { - return ( - - - -

{errorMessage}

-
-
- ); - } - return ( {jobConfig !== undefined && needsDestIndexPattern && ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts index 052068c30b84c..71e503998ed47 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts @@ -6,7 +6,11 @@ import { useState } from 'react'; import { Direction, EuiBasicTableProps, EuiTableSortingType } from '@elastic/eui'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import sortBy from 'lodash/sortBy'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; const PAGE_SIZE = 10; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 178638322bacd..59c6f7249408d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -10,7 +10,7 @@ import { memoize } from 'lodash'; import numeral from '@elastic/numeral'; import { isValidIndexName } from '../../../../../../../common/util/es_utils'; -import { collapseLiteralStrings } from '../../../../../../../../../../src/plugins/es_ui_shared/public'; +import { collapseLiteralStrings } from '../../../../../../../shared_imports'; import { Action, ACTION } from './actions'; import { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index 161dde51df43e..1c8bfafeb10ff 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -12,6 +12,7 @@ import { extractErrorMessage } from '../../../../../../../common/util/errors'; import { DeepReadonly } from '../../../../../../../common/types/common'; import { ml } from '../../../../../services/ml_api_service'; import { useMlContext } from '../../../../../contexts/ml'; +import { DuplicateIndexPatternError } from '../../../../../../../../../../src/plugins/data/public'; import { useRefreshAnalyticsList, @@ -130,19 +131,25 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const indexPatternName = destinationIndex; try { - const newIndexPattern = await mlContext.indexPatterns.make(); + await mlContext.indexPatterns.createAndSave( + { + title: indexPatternName, + }, + false, + true + ); - Object.assign(newIndexPattern, { - id: '', - title: indexPatternName, + addRequestMessage({ + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.createIndexPatternSuccessMessage', + { + defaultMessage: 'Kibana index pattern {indexPatternName} created.', + values: { indexPatternName }, + } + ), }); - - const id = await newIndexPattern.create(); - - await mlContext.indexPatterns.clearCache(); - - // id returns false if there's a duplicate index pattern. - if (id === false) { + } catch (e) { + if (e instanceof DuplicateIndexPatternError) { addRequestMessage({ error: i18n.translate( 'xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessageError', @@ -158,34 +165,17 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { } ), }); - return; - } - - // check if there's a default index pattern, if not, - // set the newly created one as the default index pattern. - if (!mlContext.kibanaConfig.get('defaultIndex')) { - await mlContext.kibanaConfig.set('defaultIndex', id); + } else { + addRequestMessage({ + error: extractErrorMessage(e), + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.createIndexPatternErrorMessage', + { + defaultMessage: 'An error occurred creating the Kibana index pattern:', + } + ), + }); } - - addRequestMessage({ - message: i18n.translate( - 'xpack.ml.dataframe.analytics.create.createIndexPatternSuccessMessage', - { - defaultMessage: 'Kibana index pattern {indexPatternName} created.', - values: { indexPatternName }, - } - ), - }); - } catch (e) { - addRequestMessage({ - error: extractErrorMessage(e), - message: i18n.translate( - 'xpack.ml.dataframe.analytics.create.createIndexPatternErrorMessage', - { - defaultMessage: 'An error occurred creating the Kibana index pattern:', - } - ), - }); } }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/geo_point.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/geo_point.tsx index 831ae8de8081a..0f6ffe44cc9f8 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/geo_point.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/geo_point.tsx @@ -5,6 +5,8 @@ */ import { i18n } from '@kbn/i18n'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import debounce from 'lodash/debounce'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { ChangeEvent, Component, Fragment } from 'react'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js index 08b61a5fa4eed..2ad0c9b1ac263 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js @@ -215,7 +215,7 @@ export class ImportView extends Component { // mappings, use this field as the time field. // This relies on the field being populated by // the ingest pipeline on ingest - if (mappings[DEFAULT_TIME_FIELD] !== undefined) { + if (mappings.properties[DEFAULT_TIME_FIELD] !== undefined) { timeFieldName = DEFAULT_TIME_FIELD; this.setState({ timeFieldName }); } @@ -615,34 +615,16 @@ export class ImportView extends Component { } } -async function createKibanaIndexPattern( - indexPatternName, - indexPatterns, - timeFieldName, - kibanaConfig -) { +async function createKibanaIndexPattern(indexPatternName, indexPatterns, timeFieldName) { try { - const emptyPattern = await indexPatterns.make(); - - Object.assign(emptyPattern, { - id: '', + const emptyPattern = await indexPatterns.createAndSave({ title: indexPatternName, timeFieldName, }); - const id = await emptyPattern.create(); - - await indexPatterns.clearCache(); - - // check if there's a default index pattern, if not, - // set the newly created one as the default index pattern. - if (!kibanaConfig.get('defaultIndex')) { - await kibanaConfig.set('defaultIndex', id); - } - return { success: true, - id, + id: emptyPattern.id, }; } catch (error) { return { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js index a6fda86f27a7c..9ca1d935c6e1e 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js @@ -11,11 +11,23 @@ * and manages the layout of the charts in the containing div. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import find from 'lodash/find'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import sortBy from 'lodash/sortBy'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import map from 'lodash/map'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import reduce from 'lodash/reduce'; import { buildConfig } from './explorer_chart_config_builder'; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js index a7d422d161108..8e9e8a03929c3 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import cloneDeep from 'lodash/cloneDeep'; import mockAnomalyChartRecords from './__mocks__/mock_anomaly_chart_records.json'; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx b/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx index 05e082711f619..f464eaf362c3a 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx @@ -10,8 +10,14 @@ import React from 'react'; import './_explorer.scss'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isEqual from 'lodash/isEqual'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import uniq from 'lodash/uniq'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; import d3 from 'd3'; import moment from 'moment'; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js index 8bc0057b27d6d..558fe4c73c597 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js @@ -7,6 +7,8 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import sortBy from 'lodash/sortBy'; import moment from 'moment'; diff --git a/x-pack/plugins/ml/public/application/services/forecast_service.js b/x-pack/plugins/ml/public/application/services/forecast_service.js index c13e265b4655c..a99c82015df4d 100644 --- a/x-pack/plugins/ml/public/application/services/forecast_service.js +++ b/x-pack/plugins/ml/public/application/services/forecast_service.js @@ -6,8 +6,14 @@ // Service for carrying out requests to run ML forecasts and to obtain // data on forecasts that have been performed. +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import find from 'lodash/find'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; import { map } from 'rxjs/operators'; diff --git a/x-pack/plugins/ml/public/application/services/job_service.js b/x-pack/plugins/ml/public/application/services/job_service.js index ea97492ae0f5a..eef760fb5d017 100644 --- a/x-pack/plugins/ml/public/application/services/job_service.js +++ b/x-pack/plugins/ml/public/application/services/job_service.js @@ -4,10 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import cloneDeep from 'lodash/cloneDeep'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import find from 'lodash/find'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isNumber from 'lodash/isNumber'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/ml/public/application/services/mapping_service.js b/x-pack/plugins/ml/public/application/services/mapping_service.js index 251bb0bce5690..76ed494995477 100644 --- a/x-pack/plugins/ml/public/application/services/mapping_service.js +++ b/x-pack/plugins/ml/public/application/services/mapping_service.js @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; import { ml } from './ml_api_service'; diff --git a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts index 22f878a337f51..8ba71ba948b15 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts @@ -13,7 +13,11 @@ // Returned response contains a results property containing the requested aggregation. import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; import { Dictionary } from '../../../../common/types/common'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.js b/x-pack/plugins/ml/public/application/services/results_service/results_service.js index fd48845494dfd..fac1ee1480172 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.js +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.js @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap b/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap index fed435d47dfc6..ad76bb9115617 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap @@ -56,6 +56,7 @@ exports[`CalendarForm Renders calendar form 1`] = ` labelType="label" > - +

{description}

@@ -116,6 +116,7 @@ export const CalendarForm = ({ value={calendarId} onChange={onCalendarIdChange} disabled={isEdit === true || saving === true} + data-test-subj="mlCalendarIdInput" /> @@ -132,6 +133,7 @@ export const CalendarForm = ({ value={description} onChange={onDescriptionChange} disabled={isEdit === true || saving === true} + data-test-subj="mlCalendarDescriptionInput" /> diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js index d80e248674a8f..0b5d2b7b5a3ea 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js @@ -257,7 +257,12 @@ export class NewEventModal extends Component { return ( - + @@ -293,13 +299,18 @@ export class NewEventModal extends Component { - + - + c.calendar_id).join(', '), + }} /> } onCancel={this.closeDestroyModal} @@ -130,18 +135,7 @@ export class CalendarsListUI extends Component { } buttonColor="danger" defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - > -

- c.calendar_id).join(', '), - }} - /> -

-
+ /> ); } diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap index 6e9cd17deabee..969406724537d 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap @@ -7,7 +7,7 @@ exports[`AddItemPopover calls addItems with multiple items on clicking Add butto button={ @@ -71,6 +72,7 @@ exports[`AddItemPopover calls addItems with multiple items on clicking Add butto grow={false} > @@ -93,7 +95,7 @@ exports[`AddItemPopover opens the popover onButtonClick 1`] = ` button={ @@ -157,6 +160,7 @@ exports[`AddItemPopover opens the popover onButtonClick 1`] = ` grow={false} > @@ -179,7 +183,7 @@ exports[`AddItemPopover renders the popover 1`] = ` button={ @@ -243,6 +248,7 @@ exports[`AddItemPopover renders the popover 1`] = ` grow={false} > diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js index 07e060d87b36a..53a3877e2f1bd 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js @@ -84,7 +84,7 @@ export class AddItemPopover extends Component { iconSide="right" onClick={this.onButtonClick} isDisabled={this.props.canCreateFilter === false} - data-test-subj="mlFilterListAddItemButton" + data-test-subj="mlFilterListOpenNewItemsPopoverButton" > } > - + @@ -127,6 +131,7 @@ export class AddItemPopover extends Component { } + data-test-subj="mlFilterListDeleteConfirmation" defaultFocusedButton="confirm" onCancel={[Function]} onConfirm={[Function]} diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js index 75fdce8e2bac8..5aafe79645f6a 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js @@ -86,6 +86,7 @@ export class DeleteFilterListModal extends Component { } buttonColor="danger" defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + data-test-subj={'mlFilterListDeleteConfirmation'} /> ); diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap index 9904e90a5afae..268b93923a432 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap @@ -47,6 +47,7 @@ exports[`FilterListUsagePopover opens the popover onButtonClick 1`] = ` labelType="label" > diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js index 06ace034ca819..b7bcb201f2438 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js @@ -102,6 +102,7 @@ export class EditDescriptionPopover extends Component { name="filter_list_description" value={value} onChange={this.onChange} + data-test-subj={'mlFilterListDescriptionInput'} /> diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap index c2fab64473228..f6a4f76975553 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap @@ -80,6 +80,7 @@ exports[`EditFilterList adds new items to filter list 1`] = ` grow={false} > - +

A test filter list

@@ -180,6 +183,7 @@ exports[`EditFilterListHeader renders the header when creating a new filter list labelType="label" > - +

A test filter list

diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js index 681c54ca9eee0..9ea470a388f02 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js @@ -362,7 +362,10 @@ export class EditFilterListUI extends Component { /> - this.returnToFiltersList()}> + this.returnToFiltersList()} + > updateNewFilterId(e.target.value)} + data-test-subj={'mlNewFilterListIdInput'} /> ); @@ -96,7 +97,7 @@ export const EditFilterListHeader = ({ if (description !== undefined && description.length > 0) { descriptionField = ( - +

{description}

); diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js b/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js index ed992b4e866ff..9e1457483cb2c 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js @@ -214,7 +214,7 @@ export function FilterListsTable({ isSelectable={true} data-test-subj="mlFilterListsTable" rowProps={(item) => ({ - 'data-test-subj': `mlFilterListsRow row-${item.filter_id}`, + 'data-test-subj': `mlFilterListRow row-${item.filter_id}`, })} />
diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js index d825844ffa14b..9f18eb1f4fed6 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js @@ -9,6 +9,8 @@ */ import PropTypes from 'prop-types'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; import React, { Component } from 'react'; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index c1afb2994c92f..78583fc4872b2 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -12,9 +12,17 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import useObservable from 'react-use/lib/useObservable'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isEqual from 'lodash/isEqual'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import reduce from 'lodash/reduce'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; import d3 from 'd3'; import moment from 'moment'; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts index 5149fecb0ec26..b8f5f08822766 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts @@ -4,9 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import find from 'lodash/find'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import filter from 'lodash/filter'; import { Observable } from 'rxjs'; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js index 7d14bb43ef811..c8c1c98e758b8 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js @@ -10,8 +10,14 @@ * Viewer dashboard. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import each from 'lodash/each'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import find from 'lodash/find'; import moment from 'moment-timezone'; diff --git a/x-pack/plugins/ml/public/application/util/chart_config_builder.js b/x-pack/plugins/ml/public/application/util/chart_config_builder.js index bc63404a106db..0c4aa4f717dbe 100644 --- a/x-pack/plugins/ml/public/application/util/chart_config_builder.js +++ b/x-pack/plugins/ml/public/application/util/chart_config_builder.js @@ -9,6 +9,8 @@ * in the source metric data. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import get from 'lodash/get'; import { mlFunctionToESAggregation } from '../../../common/util/job_utils'; diff --git a/x-pack/plugins/ml/public/application/util/index_utils.ts b/x-pack/plugins/ml/public/application/util/index_utils.ts index 192552b25d15a..42be3dd8252f9 100644 --- a/x-pack/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/plugins/ml/public/application/util/index_utils.ts @@ -104,7 +104,11 @@ export function getQueryFromSavedSearch(savedSearch: SavedSearchSavedObject) { export function getIndexPatternById(id: string): Promise { if (indexPatternsContract !== null) { - return indexPatternsContract.get(id); + if (id) { + return indexPatternsContract.get(id); + } else { + return indexPatternsContract.create({}); + } } else { throw new Error('Index patterns are not initialized!'); } diff --git a/x-pack/plugins/ml/public/application/util/time_buckets.js b/x-pack/plugins/ml/public/application/util/time_buckets.js index 15b8f24804ec8..f859b465f5de1 100644 --- a/x-pack/plugins/ml/public/application/util/time_buckets.js +++ b/x-pack/plugins/ml/public/application/util/time_buckets.js @@ -4,10 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isPlainObject from 'lodash/isPlainObject'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isString from 'lodash/isString'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import ary from 'lodash/ary'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import sortBy from 'lodash/sortBy'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import assign from 'lodash/assign'; import moment from 'moment'; import dateMath from '@elastic/datemath'; diff --git a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts index 6a44756412fe3..97ee083bedaa6 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isEmpty from 'lodash/isEmpty'; import { AnomalyDetectionQueryState, diff --git a/x-pack/plugins/ml/public/ml_url_generator/common.ts b/x-pack/plugins/ml/public/ml_url_generator/common.ts index f929e513e618a..59dddeed29888 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/common.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/common.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isEmpty from 'lodash/isEmpty'; import { MlGenericUrlState } from '../../common/types/ml_url_generator'; import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public'; diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts index d1a4df768a6ae..394dff1408134 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts @@ -5,13 +5,13 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { IIndexPattern } from 'src/plugins/data/server'; +import { IndexPatternAttributes } from 'src/plugins/data/server'; export class IndexPatternHandler { constructor(private savedObjectsClient: SavedObjectsClientContract) {} // returns a id based on an index pattern name async getIndexPatternId(indexName: string) { - const response = await this.savedObjectsClient.find({ + const response = await this.savedObjectsClient.find({ type: 'index-pattern', perPage: 10, search: `"${indexName}"`, diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json index b4fb242f16522..40c47352371d4 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json @@ -14,7 +14,11 @@ "use_null": true } ], - "influencers": ["event.dataset", "mlcategory"] + "influencers": ["event.dataset", "mlcategory"], + "per_partition_categorization": { + "enabled": true, + "stop_on_warn": false + } }, "analysis_limits": { "model_memory_limit": "100mb", @@ -29,6 +33,6 @@ }, "custom_settings": { "created_by": "ml-module-logs-ui-categories", - "job_revision": 0 + "job_revision": 1 } } diff --git a/x-pack/plugins/ml/shared_imports.ts b/x-pack/plugins/ml/shared_imports.ts index a82ed5387818d..33669a082f7f0 100644 --- a/x-pack/plugins/ml/shared_imports.ts +++ b/x-pack/plugins/ml/shared_imports.ts @@ -3,9 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { XJson } from '../../../src/plugins/es_ui_shared/public'; +const { collapseLiteralStrings, expandLiteralStrings } = XJson; -export { - XJsonMode, - collapseLiteralStrings, - expandLiteralStrings, -} from '../../../src/plugins/es_ui_shared/public'; +export { XJsonMode } from '@kbn/ace'; +export { collapseLiteralStrings, expandLiteralStrings }; diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 2c714080969e4..8be0eb0b06823 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -273,3 +273,10 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email'; export const ALERT_ACTION_TYPE_LOG = '.server-log'; export const ALERT_EMAIL_SERVICES = ['gmail', 'hotmail', 'icloud', 'outlook365', 'ses', 'yahoo']; + +/** + * The saved object type for various monitoring data + */ +export const SAVED_OBJECT_TELEMETRY = 'monitoring-telemetry'; + +export const TELEMETRY_METRIC_BUTTON_CLICK = 'btnclick__'; diff --git a/x-pack/plugins/monitoring/kibana.json b/x-pack/plugins/monitoring/kibana.json index 2b8756ea0cb46..8b0b0b7aae693 100644 --- a/x-pack/plugins/monitoring/kibana.json +++ b/x-pack/plugins/monitoring/kibana.json @@ -12,7 +12,8 @@ "triggers_actions_ui", "alerts", "actions", - "encryptedSavedObjects" + "encryptedSavedObjects", + "observability" ], "optionalPlugins": ["infra", "telemetryCollectionManager", "usageCollection", "home", "cloud"], "server": true, diff --git a/x-pack/plugins/monitoring/public/angular/app_modules.ts b/x-pack/plugins/monitoring/public/angular/app_modules.ts index 499610045d771..4ef905fd35fc4 100644 --- a/x-pack/plugins/monitoring/public/angular/app_modules.ts +++ b/x-pack/plugins/monitoring/public/angular/app_modules.ts @@ -41,10 +41,6 @@ import { licenseProvider } from '../services/license'; // @ts-ignore import { titleProvider } from '../services/title'; // @ts-ignore -import { monitoringBeatsBeatProvider } from '../directives/beats/beat'; -// @ts-ignore -import { monitoringBeatsOverviewProvider } from '../directives/beats/overview'; -// @ts-ignore import { monitoringMlListingProvider } from '../directives/elasticsearch/ml_job_listing'; // @ts-ignore import { monitoringMainProvider } from '../directives/main'; @@ -153,8 +149,6 @@ function createMonitoringAppServices() { function createMonitoringAppDirectives() { angular .module('monitoring/directives', []) - .directive('monitoringBeatsBeat', monitoringBeatsBeatProvider) - .directive('monitoringBeatsOverview', monitoringBeatsOverviewProvider) .directive('monitoringMlListing', monitoringMlListingProvider) .directive('monitoringMain', monitoringMainProvider); } diff --git a/x-pack/plugins/monitoring/public/angular/index.ts b/x-pack/plugins/monitoring/public/angular/index.ts index da57c028643a5..3c30d3c358a14 100644 --- a/x-pack/plugins/monitoring/public/angular/index.ts +++ b/x-pack/plugins/monitoring/public/angular/index.ts @@ -26,6 +26,7 @@ export class AngularApp { pluginInitializerContext, externalConfig, triggersActionsUi, + usageCollection, kibanaLegacy, } = deps; const app: IModule = localAppModule(deps); @@ -42,6 +43,7 @@ export class AngularApp { externalConfig, kibanaLegacy, triggersActionsUi, + usageCollection, }, this.injector ); diff --git a/x-pack/plugins/monitoring/public/components/apm/instance/instance.js b/x-pack/plugins/monitoring/public/components/apm/instance/instance.js index 396d2258edd0c..eec24e741ac41 100644 --- a/x-pack/plugins/monitoring/public/components/apm/instance/instance.js +++ b/x-pack/plugins/monitoring/public/components/apm/instance/instance.js @@ -42,9 +42,7 @@ export function ApmServerInstance({ summary, metrics, ...props }) { const charts = seriesToShow.map((data, index) => ( - - - + )); @@ -55,15 +53,15 @@ export function ApmServerInstance({ summary, metrics, ...props }) {

+ + + + - - - - {charts} diff --git a/x-pack/plugins/monitoring/public/components/apm/instances/instances.js b/x-pack/plugins/monitoring/public/components/apm/instances/instances.js index 6dcfa6dd043aa..e05ba1878caed 100644 --- a/x-pack/plugins/monitoring/public/components/apm/instances/instances.js +++ b/x-pack/plugins/monitoring/public/components/apm/instances/instances.js @@ -156,11 +156,11 @@ export function ApmServerInstances({ apms, setupMode }) { /> + + + + - - - - {setupModeCallout} ( - - - + )); @@ -51,15 +49,15 @@ export function ApmOverview({ stats, metrics, ...props }) {

+ + + + - - - - {charts} diff --git a/x-pack/plugins/monitoring/public/components/beats/beat/beat.js b/x-pack/plugins/monitoring/public/components/beats/beat/beat.js index 3fe211c0f2edc..f489271659bfe 100644 --- a/x-pack/plugins/monitoring/public/components/beats/beat/beat.js +++ b/x-pack/plugins/monitoring/public/components/beats/beat/beat.js @@ -135,6 +135,9 @@ export function Beat({ summary, metrics, ...props }) { + + + diff --git a/x-pack/plugins/monitoring/public/components/beats/listing/listing.js b/x-pack/plugins/monitoring/public/components/beats/listing/listing.js index be8595e8e6bbe..60a35e00a4c63 100644 --- a/x-pack/plugins/monitoring/public/components/beats/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/beats/listing/listing.js @@ -13,6 +13,7 @@ import { EuiSpacer, EuiLink, EuiScreenReaderOnly, + EuiPanel, } from '@elastic/eui'; import { Stats } from '../../beats'; import { formatMetric } from '../../../lib/format_number'; @@ -153,9 +154,11 @@ export class Listing extends PureComponent { /> - + - + + + {setupModeCallOut} - + + + + - - -

- -

-
- - -
+ +

+ +

+
+ +
- - -

- -

-
- - -
+ +

+ +

+
+ +
- - -

- -

-
- - -
+ +

+ +

+
+ +
- +
+ + @@ -212,18 +213,25 @@ exports[`Overview that overview page shows a message if there is no beats data 1 /> - + + + + - + + + diff --git a/x-pack/plugins/monitoring/public/components/beats/overview/overview.js b/x-pack/plugins/monitoring/public/components/beats/overview/overview.js index 83f92ea1b481c..897f017f44f41 100644 --- a/x-pack/plugins/monitoring/public/components/beats/overview/overview.js +++ b/x-pack/plugins/monitoring/public/components/beats/overview/overview.js @@ -30,46 +30,40 @@ function renderLatestActive(latestActive, latestTypes, latestVersions) { return ( - - -

- -

-
- - -
+ +

+ +

+
+ +
- - -

- -

-
- - -
+ +

+ +

+
+ +
- - -

- -

-
- - -
+ +

+ +

+
+ +
); @@ -118,10 +112,13 @@ export function BeatsOverview({ /> - + - {renderLatestActive(latestActive, latestTypes, latestVersions)} - + + + {renderLatestActive(latestActive, latestTypes, latestVersions)} + + {charts}
diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js index ccbf0b0ec711d..4bf07710393ea 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js @@ -55,7 +55,7 @@ export function ApmPanel(props) { {...props} url="apm" title={i18n.translate('xpack.monitoring.cluster.overview.apmPanel.apmTitle', { - defaultMessage: 'APM', + defaultMessage: 'APM server', })} > @@ -70,21 +70,21 @@ export function ApmPanel(props) { aria-label={i18n.translate( 'xpack.monitoring.cluster.overview.apmPanel.overviewLinkAriaLabel', { - defaultMessage: 'APM Overview', + defaultMessage: 'APM server overview', } )} data-test-subj="apmOverview" > - + {formatMetric(props.totalEvents, '0.[0]a')} - + {apmsTotal} }} /> @@ -144,7 +144,7 @@ export function ApmPanel(props) {
- + - + {formatMetric(props.totalEvents, '0.[0]a')} - + {props.logs.types.map((log, index) => ( - + - + @@ -276,7 +277,7 @@ export function ElasticsearchPanel(props) { - + - + - + - + {showMlJobs()} - + - + - + - + - + {formatNumber(get(indices, 'docs.count'), 'int_commas')} - + - + - + - + diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js index 6fa533302db48..7df0a3ca7138e 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js @@ -111,7 +111,7 @@ export function KibanaPanel(props) { data-test-subj="kibana_overview" data-overview-status={props.status} > - + {props.requests_total} - + - + {formatNumber(props.concurrent_connections, 'int_commas')} - + - + {formatNumber(props.events_in_total, '0.[0]a')} - + - + {props.max_uptime ? formatNumber(props.max_uptime, 'time_since') : 0} - + - + {queueTypes[LOGSTASH.QUEUE_TYPES.MEMORY] || 0} - + } checked={showSystemIndices} diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap index c7081dc439085..b0b5ceb46d16c 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap @@ -10,30 +10,46 @@ exports[`Node Listing Metric Cell should format N/A as the metric for an offline exports[`Node Listing Metric Cell should format a non-percentage metric 1`] = `
+
- - 206.3 GB  - - -
- 206.5 GB max -
- 206.3 GB min +
+
+
+
+
+
+
+
+
+ 206.3 GB +
+
@@ -41,30 +57,46 @@ exports[`Node Listing Metric Cell should format a non-percentage metric 1`] = ` exports[`Node Listing Metric Cell should format a percentage metric 1`] = `
+
- - 0%  - - -
- 2% max -
- 0% min +
+
+
+
+
+
+
+
+
+ 0% +
+
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/cells.test.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/cells.test.js index 0c4b4b2b3c3f4..f0b131b65433c 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/cells.test.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/cells.test.js @@ -27,6 +27,7 @@ describe('Node Listing Metric Cell', () => { }, summary: { minVal: 0, maxVal: 2, lastVal: 0, slope: -1 }, }, + 'data-test-subj': 'testCell', }; expect(renderWithIntl()).toMatchSnapshot(); }); @@ -54,6 +55,7 @@ describe('Node Listing Metric Cell', () => { slope: -1, }, }, + 'data-test-subj': 'testCell2', }; expect(renderWithIntl()).toMatchSnapshot(); }); diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js index 4c3b642213d99..9956dd4da7d8a 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js @@ -4,19 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useState } from 'react'; import { get } from 'lodash'; import { formatMetric } from '../../../lib/format_number'; -import { EuiText, EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiText, + EuiPopover, + EuiIcon, + EuiDescriptionList, + EuiSpacer, + EuiKeyboardAccessible, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +const TRENDING_DOWN = i18n.translate('xpack.monitoring.elasticsearch.node.cells.trendingDownText', { + defaultMessage: 'down', +}); +const TRENDING_UP = i18n.translate('xpack.monitoring.elasticsearch.node.cells.trendingUpText', { + defaultMessage: 'up', +}); + function OfflineCell() { return
N/A
; } -const getSlopeArrow = (slope) => { +const getDirection = (slope) => { + if (slope || slope === 0) { + return slope > 0 ? TRENDING_UP : TRENDING_DOWN; + } + return null; +}; + +const getIcon = (slope) => { if (slope || slope === 0) { - return slope > 0 ? 'up' : 'down'; + return slope > 0 ? 'arrowUp' : 'arrowDown'; } return null; }; @@ -28,40 +51,82 @@ const metricVal = (metric, format, isPercent, units) => { return formatMetric(metric, format, units); }; -const noWrapStyle = { overflowX: 'hidden', whiteSpace: 'nowrap' }; - function MetricCell({ isOnline, metric = {}, isPercent, ...props }) { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const onButtonClick = () => setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen); + const closePopover = () => setIsPopoverOpen(false); + if (isOnline) { const { lastVal, maxVal, minVal, slope } = get(metric, 'summary', {}); const format = get(metric, 'metric.format'); const units = get(metric, 'metric.units'); + const tooltipItems = [ + { + title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.trending', { + defaultMessage: 'Trending', + }), + description: getDirection(slope), + }, + { + title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.max', { + defaultMessage: 'Max value', + }), + description: metricVal(maxVal, format, isPercent, units), + }, + { + title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.min', { + defaultMessage: 'Min value', + }), + description: metricVal(minVal, format, isPercent, units), + }, + ]; + + const button = ( + + + + ); + return ( - + + - - - {metricVal(lastVal, format, isPercent)} -   - - - - - {i18n.translate('xpack.monitoring.elasticsearch.nodes.cells.maxText', { - defaultMessage: '{metric} max', - values: { - metric: metricVal(maxVal, format, isPercent, units), - }, - })} - - - {i18n.translate('xpack.monitoring.elasticsearch.nodes.cells.minText', { - defaultMessage: '{metric} min', - values: { - metric: metricVal(minVal, format, isPercent, units), - }, - })} - + + + +
+ + + + {i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.preface', { + defaultMessage: 'Applies to current time period', + })} + +
+
+
+ + {metricVal(lastVal, format, isPercent)} + +
); diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 43512f8e528f6..f088f7c0d348a 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -73,7 +73,6 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.nameColumnTitle', { defaultMessage: 'Name', }), - width: '20%', field: 'name', sortable: true, render: (value, node) => { @@ -131,7 +130,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler defaultMessage: 'Alerts', }), field: 'alerts', - width: '175px', + // width: '175px', sortable: true, render: (_field, node) => { return ( @@ -148,6 +147,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumnTitle', { defaultMessage: 'Status', }), + dataType: 'boolean', field: 'isOnline', sortable: true, render: (value) => { @@ -181,22 +181,18 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.shardsColumnTitle', { defaultMessage: 'Shards', }), + dataType: 'number', field: 'shardCount', sortable: true, render: (value, node) => { - return node.isOnline ? ( -
- {value} -
- ) : ( - - ); + return node.isOnline ? {value} : ; }, }); if (showCgroupMetricsElasticsearch) { cols.push({ name: cpuUsageColumnTitle, + dataType: 'number', field: 'node_cgroup_quota', sortable: getSortHandler('node_cgroup_quota'), render: (value, node) => ( @@ -213,6 +209,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.cpuThrottlingColumnTitle', { defaultMessage: 'CPU Throttling', }), + dataType: 'number', field: 'node_cgroup_throttled', sortable: getSortHandler('node_cgroup_throttled'), render: (value, node) => ( @@ -227,6 +224,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler } else { cols.push({ name: cpuUsageColumnTitle, + dataType: 'number', field: 'node_cpu_utilization', sortable: getSortHandler('node_cpu_utilization'), render: (value, node) => { @@ -245,6 +243,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.loadAverageColumnTitle', { defaultMessage: 'Load Average', }), + dataType: 'number', field: 'node_load_average', sortable: getSortHandler('node_load_average'), render: (value, node) => ( @@ -265,6 +264,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler javaVirtualMachine: 'JVM', }, }), + dataType: 'number', field: 'node_jvm_mem_percent', sortable: getSortHandler('node_jvm_mem_percent'), render: (value, node) => ( @@ -281,6 +281,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.diskFreeSpaceColumnTitle', { defaultMessage: 'Disk Free Space', }), + dataType: 'number', field: 'node_free_space', sortable: getSortHandler('node_free_space'), render: (value, node) => ( diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/table_head.js b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/table_head.js index fd5f28ea02039..3c875667fe04c 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/table_head.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/table_head.js @@ -5,6 +5,7 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -37,7 +38,12 @@ class IndexLabel extends React.Component { @@ -45,3 +46,18 @@ export function PageLoading() { ); } + +function PageLoadingTracking({ pageViewTitle }) { + const path = pageViewTitle.toLowerCase().replace(/-/g, '').replace(/\s+/g, '_'); + useTrackPageview({ app: 'stack_monitoring', path }); + useTrackPageview({ app: 'stack_monitoring', path, delay: 15000 }); + return ; +} + +export function PageLoading({ pageViewTitle }) { + if (pageViewTitle) { + return ; + } + + return ; +} diff --git a/x-pack/plugins/monitoring/public/components/setup_mode/enter_button.scss b/x-pack/plugins/monitoring/public/components/setup_mode/enter_button.scss index a5ab07618f267..e19b4fa760641 100644 --- a/x-pack/plugins/monitoring/public/components/setup_mode/enter_button.scss +++ b/x-pack/plugins/monitoring/public/components/setup_mode/enter_button.scss @@ -1,6 +1,3 @@ .monSetupModeEnterButton__buttonWrapper { - position: absolute; - top: $euiSize; - left: $euiSizeM; - z-index: 1; + padding: $euiSizeM; } diff --git a/x-pack/plugins/monitoring/public/components/setup_mode/enter_button.tsx b/x-pack/plugins/monitoring/public/components/setup_mode/enter_button.tsx index e06113255c1ef..b47b51e664f5f 100644 --- a/x-pack/plugins/monitoring/public/components/setup_mode/enter_button.tsx +++ b/x-pack/plugins/monitoring/public/components/setup_mode/enter_button.tsx @@ -8,6 +8,8 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import './enter_button.scss'; +import { METRIC_TYPE, useUiTracker } from '../../../../observability/public'; +import { TELEMETRY_METRIC_BUTTON_CLICK } from '../../../common/constants'; export interface SetupModeEnterButtonProps { enabled: boolean; @@ -18,6 +20,7 @@ export const SetupModeEnterButton: React.FC = ( props: SetupModeEnterButtonProps ) => { const [isLoading, setIsLoading] = React.useState(false); + const trackStat = useUiTracker({ app: 'stack_monitoring' }); if (!props.enabled) { return null; @@ -26,6 +29,10 @@ export const SetupModeEnterButton: React.FC = ( async function enterSetupMode() { setIsLoading(true); await props.toggleSetupMode(true); + trackStat({ + metric: `${TELEMETRY_METRIC_BUTTON_CLICK}setupmode_enter`, + metricType: METRIC_TYPE.CLICK, + }); setIsLoading(false); } diff --git a/x-pack/plugins/monitoring/public/directives/beats/beat/index.js b/x-pack/plugins/monitoring/public/directives/beats/beat/index.js deleted file mode 100644 index 103cac98ba564..0000000000000 --- a/x-pack/plugins/monitoring/public/directives/beats/beat/index.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { Beat } from '../../../components/beats/beat'; - -//monitoringBeatsBeat -export function monitoringBeatsBeatProvider() { - return { - restrict: 'E', - scope: { - data: '=', - onBrush: '<', - zoomInfo: '<', - }, - link(scope, $el) { - scope.$on('$destroy', () => $el && $el[0] && unmountComponentAtNode($el[0])); - - scope.$watch('data', (data = {}) => { - render( - , - $el[0] - ); - }); - }, - }; -} diff --git a/x-pack/plugins/monitoring/public/directives/beats/overview/index.js b/x-pack/plugins/monitoring/public/directives/beats/overview/index.js deleted file mode 100644 index 4faf69e13d02c..0000000000000 --- a/x-pack/plugins/monitoring/public/directives/beats/overview/index.js +++ /dev/null @@ -1,30 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { BeatsOverview } from '../../../components/beats/overview'; - -export function monitoringBeatsOverviewProvider() { - return { - restrict: 'E', - scope: { - data: '=', - onBrush: '<', - zoomInfo: '<', - }, - link(scope, $el) { - scope.$on('$destroy', () => $el && $el[0] && unmountComponentAtNode($el[0])); - - scope.$watch('data', (data = {}) => { - render( - , - $el[0] - ); - }); - }, - }; -} diff --git a/x-pack/plugins/monitoring/public/directives/main/index.html b/x-pack/plugins/monitoring/public/directives/main/index.html index fabd207d72b1f..fb24d9e678d56 100644 --- a/x-pack/plugins/monitoring/public/directives/main/index.html +++ b/x-pack/plugins/monitoring/public/directives/main/index.html @@ -1,19 +1,32 @@
-
- - +
+
+
+
+
+
+

{{pageTitle || monitoringMain.instance}}

+
+
+
+
+
+ + +
+
`; diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx index 62880e7510cd2..0cb721bb5382f 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx @@ -15,6 +15,8 @@ import { Title } from './title'; import { DraggableArguments, BadgeOptions, TitleProp } from './types'; import { useFormatUrl } from '../link_to'; import { SecurityPageName } from '../../../app/types'; +import { Sourcerer } from '../sourcerer'; +import { SourcererScopeName } from '../../store/sourcerer/model'; interface HeaderProps { border?: boolean; @@ -72,6 +74,7 @@ export interface HeaderPageProps extends HeaderProps { badgeOptions?: BadgeOptions; children?: React.ReactNode; draggableArguments?: DraggableArguments; + hideSourcerer?: boolean; subtitle?: SubtitleProps['items']; subtitle2?: SubtitleProps['items']; title: TitleProp; @@ -84,6 +87,7 @@ const HeaderPageComponent: React.FC = ({ border, children, draggableArguments, + hideSourcerer = false, isLoading, subtitle, subtitle2, @@ -138,6 +142,7 @@ const HeaderPageComponent: React.FC = ({ )} + {!hideSourcerer && } ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx index 9473ba67a1c4f..cc0c4d4c837a3 100644 --- a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { getEmptyValue } from '../empty_value'; -import { LastEventIndexKey } from '../../../graphql/types'; +import { LastEventIndexKey } from '../../../../common/search_strategy'; import { mockLastEventTimeQuery } from '../../containers/events/last_event_time/mock'; import { useMountAppended } from '../../utils/use_mount_appended'; @@ -37,7 +37,7 @@ describe('Last Event Time Stat', () => { ]); const wrapper = mount( - + ); expect(wrapper.html()).toBe( @@ -48,13 +48,13 @@ describe('Last Event Time Stat', () => { (useTimelineLastEventTime as jest.Mock).mockReturnValue([ false, { - lastSeen: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.lastSeen, - errorMessage: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.errorMessage, + lastSeen: mockLastEventTimeQuery.lastSeen, + errorMessage: mockLastEventTimeQuery.errorMessage, }, ]); const wrapper = mount( - + ); expect(wrapper.html()).toBe('Last event: 12 minutes ago'); @@ -64,12 +64,12 @@ describe('Last Event Time Stat', () => { false, { lastSeen: 'something-invalid', - errorMessage: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.errorMessage, + errorMessage: mockLastEventTimeQuery.errorMessage, }, ]); const wrapper = mount( - + ); @@ -80,12 +80,12 @@ describe('Last Event Time Stat', () => { false, { lastSeen: null, - errorMessage: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.errorMessage, + errorMessage: mockLastEventTimeQuery.errorMessage, }, ]); const wrapper = mount( - + ); expect(wrapper.html()).toContain(getEmptyValue()); diff --git a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx index e9e8e7a03017c..fe827b3ab324c 100644 --- a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx @@ -8,58 +8,64 @@ import { EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { memo } from 'react'; -import { LastEventIndexKey } from '../../../graphql/types'; +import { DocValueFields, LastEventIndexKey } from '../../../../common/search_strategy'; import { useTimelineLastEventTime } from '../../containers/events/last_event_time'; import { getEmptyTagValue } from '../empty_value'; import { FormattedRelativePreferenceDate } from '../formatted_date'; export interface LastEventTimeProps { + docValueFields: DocValueFields[]; hostName?: string; indexKey: LastEventIndexKey; ip?: string; + indexNames: string[]; } -export const LastEventTime = memo(({ hostName, indexKey, ip }) => { - const [loading, { lastSeen, errorMessage }] = useTimelineLastEventTime({ - indexKey, - details: { - hostName, - ip, - }, - }); +export const LastEventTime = memo( + ({ docValueFields, hostName, indexKey, ip, indexNames }) => { + const [loading, { lastSeen, errorMessage }] = useTimelineLastEventTime({ + docValueFields, + indexKey, + indexNames, + details: { + hostName, + ip, + }, + }); + + if (errorMessage != null) { + return ( + + + + ); + } - if (errorMessage != null) { return ( - - - + <> + {loading && } + {!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date' + ? lastSeen + : !loading && + lastSeen != null && ( + , + }} + /> + )} + {!loading && lastSeen == null && getEmptyTagValue()} + ); } - - return ( - <> - {loading && } - {!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date' - ? lastSeen - : !loading && - lastSeen != null && ( - , - }} - /> - )} - {!loading && lastSeen == null && getEmptyTagValue()} - - ); -}); +); LastEventTime.displayName = 'LastEventTime'; diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx index 7286c6b743692..99dc8a802b33d 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx @@ -47,6 +47,7 @@ describe('Matrix Histogram Component', () => { errorMessage: 'error', histogramType: MatrixHistogramType.alerts, id: 'mockId', + indexNames: [], isInspected: false, isPtrIncluded: false, setQuery: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 485ca4c93133a..e7d7e60a3c408 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -37,7 +37,6 @@ export type MatrixHistogramComponentProps = MatrixHistogramProps & hideHistogramIfEmpty?: boolean; histogramType: MatrixHistogramType; id: string; - indexToAdd?: string[] | null; legendPosition?: Position; mapping?: MatrixHistogramMappingTypes; showSpacer?: boolean; @@ -72,7 +71,7 @@ export const MatrixHistogramComponent: React.FC = histogramType, hideHistogramIfEmpty = false, id, - indexToAdd, + indexNames, legendPosition, mapping, panelHeight = DEFAULT_PANEL_HEIGHT, @@ -136,7 +135,7 @@ export const MatrixHistogramComponent: React.FC = errorMessage, filterQuery, histogramType, - indexToAdd, + indexNames, startDate, stackByField: selectedStackByOption.value, }); diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index fc1df4d8ca85f..9a892110bde43 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts @@ -59,6 +59,7 @@ interface MatrixHistogramBasicProps { export interface MatrixHistogramQueryProps { endDate: string; errorMessage: string; + indexNames: string[]; filterQuery?: ESQuery | string | undefined; setAbsoluteRangeDatePicker?: ActionCreator<{ id: InputsModelId; @@ -68,7 +69,6 @@ export interface MatrixHistogramQueryProps { setAbsoluteRangeDatePickerTarget?: InputsModelId; stackByField: string; startDate: string; - indexToAdd?: string[] | null; histogramType: MatrixHistogramType; } diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/utils.test.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/utils.test.ts index 7a3f44d3ea729..03fa55a3c9fa6 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/utils.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/utils.test.ts @@ -13,7 +13,7 @@ import { } from './utils'; import { UpdateDateRange } from '../charts/common'; import { Position } from '@elastic/charts'; -import { MatrixOverTimeHistogramData } from '../../../graphql/types'; +import { MatrixHistogramData } from '../../../../common/search_strategy'; import { BarchartConfigs } from './types'; describe('utils', () => { @@ -77,7 +77,7 @@ describe('utils', () => { describe('formatToChartDataItem', () => { test('it should format data correctly', () => { - const data: [string, MatrixOverTimeHistogramData[]] = [ + const data: [string, MatrixHistogramData[]] = [ 'g1', [ { x: 1, y: 2, g: 'g1' }, diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/utils.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/utils.ts index 9474929d35a51..5b5b56cf0ec45 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/utils.ts @@ -8,7 +8,7 @@ import { get, groupBy, map, toPairs } from 'lodash/fp'; import { UpdateDateRange, ChartSeriesData } from '../charts/common'; import { MatrixHistogramMappingTypes, BarchartConfigs } from './types'; -import { MatrixOverTimeHistogramData } from '../../../graphql/types'; +import { MatrixHistogramData } from '../../../../common/search_strategy'; import { histogramDateTimeFormatter } from '../utils'; interface GetBarchartConfigsProps { @@ -84,14 +84,14 @@ export const defaultLegendColors = [ export const formatToChartDataItem = ([key, value]: [ string, - MatrixOverTimeHistogramData[] + MatrixHistogramData[] ]): ChartSeriesData => ({ key, value, }); export const getCustomChartData = ( - data: MatrixOverTimeHistogramData[] | null, + data: MatrixHistogramData[] | null, mapping?: MatrixHistogramMappingTypes ): ChartSeriesData[] => { if (!data) return []; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index 89aa77106933e..da5099f61e9b2 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -105,6 +105,7 @@ const getMockObject = ( }, }, }, + sourcerer: {}, }); const getUrlForAppMock = (appId: string, options?: { path?: string; absolute?: boolean }) => @@ -130,7 +131,7 @@ describe('Navigation Breadcrumbs', () => { }, { href: - "securitySolution:hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", text: 'Hosts', }, { @@ -150,7 +151,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Network', href: - "securitySolution:network?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Flows', @@ -169,7 +170,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Timelines', href: - "securitySolution:timelines?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -184,12 +185,12 @@ describe('Navigation Breadcrumbs', () => { { text: 'Hosts', href: - "securitySolution:hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'siem-kibana', href: - "securitySolution:hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Authentications', href: '' }, ]); @@ -205,11 +206,11 @@ describe('Navigation Breadcrumbs', () => { { text: 'Network', href: - "securitySolution:network?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: ipv4, - href: `securitySolution:network/ip/${ipv4}/source?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolution:network/ip/${ipv4}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, { text: 'Flows', href: '' }, ]); @@ -225,11 +226,11 @@ describe('Navigation Breadcrumbs', () => { { text: 'Network', href: - "securitySolution:network?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: ipv6, - href: `securitySolution:network/ip/${ipv6Encoded}/source?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolution:network/ip/${ipv6Encoded}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, { text: 'Flows', href: '' }, ]); @@ -245,7 +246,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Detections', href: - "securitySolution:detections?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:detections?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -259,7 +260,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Cases', href: - "securitySolution:case?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -280,11 +281,11 @@ describe('Navigation Breadcrumbs', () => { { text: 'Cases', href: - "securitySolution:case?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: sampleCase.name, - href: `securitySolution:case/${sampleCase.id}?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolution:case/${sampleCase.id}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, ]); }); @@ -311,12 +312,12 @@ describe('Navigation Breadcrumbs', () => { { text: 'Hosts', href: - "securitySolution:hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'siem-kibana', href: - "securitySolution:hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Authentications', href: '' }, ]); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts index 8f5a3ac63fa1a..ed71f55fd0161 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts @@ -19,12 +19,19 @@ import { import { Query, Filter } from '../../../../../../../src/plugins/data/public'; import { SearchNavTab } from './types'; +import { SourcererScopePatterns } from '../../store/sourcerer/model'; export const getSearch = (tab: SearchNavTab, urlState: UrlState): string => { if (tab && tab.urlKey != null && URL_STATE_KEYS[tab.urlKey] != null) { return URL_STATE_KEYS[tab.urlKey].reduce( (myLocation: Location, urlKey: KeyUrlState) => { - let urlStateToReplace: UrlInputsModel | Query | Filter[] | TimelineUrl | string = ''; + let urlStateToReplace: + | Filter[] + | Query + | SourcererScopePatterns + | TimelineUrl + | UrlInputsModel + | string = ''; if (urlKey === CONSTANTS.appQuery && urlState.query != null) { if (urlState.query.query === '') { @@ -40,6 +47,8 @@ export const getSearch = (tab: SearchNavTab, urlState: UrlState): string => { } } else if (urlKey === CONSTANTS.timerange) { urlStateToReplace = urlState[CONSTANTS.timerange]; + } else if (urlKey === CONSTANTS.sourcerer) { + urlStateToReplace = urlState[CONSTANTS.sourcerer]; } else if (urlKey === CONSTANTS.timeline && urlState[CONSTANTS.timeline] != null) { const timeline = urlState[CONSTANTS.timeline]; if (timeline.id === '') { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx index 16cb19f5a0c14..102ed7851e57d 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx @@ -78,6 +78,7 @@ describe('SIEM Navigation', () => { }, [CONSTANTS.appQuery]: { query: '', language: 'kuery' }, [CONSTANTS.filters]: [], + [CONSTANTS.sourcerer]: {}, [CONSTANTS.timeline]: { id: '', isOpen: false, @@ -145,6 +146,7 @@ describe('SIEM Navigation', () => { pageName: 'hosts', pathName: '/', search: '', + sourcerer: {}, state: undefined, tabName: 'authentications', query: { query: '', language: 'kuery' }, @@ -252,6 +254,7 @@ describe('SIEM Navigation', () => { query: { language: 'kuery', query: '' }, savedQuery: undefined, search: '', + sourcerer: {}, state: undefined, tabName: 'authentications', timeline: { id: '', isOpen: false, graphEventId: '' }, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx index 5ee35e7da0f3e..b149488ff38a7 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx @@ -40,19 +40,20 @@ export const SiemNavigationComponent: React.FC< if (pathName || pageName) { setBreadcrumbs( { - query: urlState.query, detailName, filters: urlState.filters, + flowTarget, navTabs, pageName, pathName, + query: urlState.query, savedQuery: urlState.savedQuery, search, + sourcerer: urlState.sourcerer, + state, tabName, - flowTarget, - timerange: urlState.timerange, timeline: urlState.timeline, - state, + timerange: urlState.timerange, }, chrome, getUrlForApp @@ -69,6 +70,7 @@ export const SiemNavigationComponent: React.FC< navTabs={navTabs} pageName={pageName} pathName={pathName} + sourcerer={urlState.sourcerer} savedQuery={urlState.savedQuery} tabName={tabName} timeline={urlState.timeline} diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx index b25cf3779801b..5c69edbabdc66 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx @@ -68,6 +68,7 @@ describe('Tab Navigation', () => { }, [CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' }, [CONSTANTS.filters]: [], + [CONSTANTS.sourcerer]: {}, [CONSTANTS.timeline]: { id: '', isOpen: false, @@ -126,6 +127,7 @@ describe('Tab Navigation', () => { }, [CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' }, [CONSTANTS.filters]: [], + [CONSTANTS.sourcerer]: {}, [CONSTANTS.timeline]: { id: '', isOpen: false, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx index 217ad0e58570f..3eb66b5591b85 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx @@ -94,10 +94,17 @@ export const TabNavigationComponent = (props: TabNavigationProps) => { () => Object.values(navTabs).map((tab) => { const isSelected = selectedTabId === tab.id; - const { query, filters, savedQuery, timerange, timeline } = props; - const search = getSearch(tab, { query, filters, savedQuery, timerange, timeline }); + const { filters, query, savedQuery, sourcerer, timeline, timerange } = props; + const search = getSearch(tab, { + filters, + query, + savedQuery, + sourcerer, + timeline, + timerange, + }); const hrefWithSearch = - tab.href + getSearch(tab, { query, filters, savedQuery, timerange, timeline }); + tab.href + getSearch(tab, { filters, query, savedQuery, sourcerer, timeline, timerange }); return ( ` - /* dirty hack to fix draggables with tooltip on FF */ - body#siem-app { - position: static; - } - /* end of dirty hack to fix draggables with tooltip on FF */ - div.app-wrapper { background-color: rgba(0,0,0,0); } div.application { background-color: rgba(0,0,0,0); + + // Security App wrapper + > div { + display: flex; + flex: 1 1 auto; + } } .euiPopover__panel.euiPopover__panel-isOpen { @@ -67,37 +76,8 @@ export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimar ${({ theme }) => `background-color: ${theme.eui.euiColorPrimary} !important`}; } - body { - overflow-y: hidden; - } - - #kibana-body { - height: 100%; - overflow-y: hidden; - - > .content { - height: 100%; - - > .app-wrapper { - height: 100%; - - > .app-wrapper-panel { - height: 100%; - - > .application { - height: 100%; - - > div { - height: 100%; - } - } - } - } - } - } - - .${SCROLLING_DISABLED_CLASS_NAME} #kibana-body { - overflow-y: hidden; + .${SCROLLING_DISABLED_CLASS_NAME} ${SecuritySolutionAppWrapper} { + max-height: calc(100vh - ${GLOBAL_HEADER_HEIGHT}px); } `; diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx index c330bb073b146..bd9f2677ec966 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx @@ -6,107 +6,141 @@ import React from 'react'; import { mount } from 'enzyme'; -import { SecurityPageName } from '../../containers/sourcerer/constants'; -import { mockPatterns, mockSourceGroup } from '../../containers/sourcerer/mocks'; -import { MaybeSourcerer } from './index'; -import * as i18n from './translations'; -import { ADD_INDEX_PATH } from '../../../../common/constants'; +import { SourcererScopeName } from '../../store/sourcerer/model'; +import { SourcererComponent } from './index'; +import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; +import { sourcererActions, sourcererModel } from '../../store/sourcerer'; +import { + apolloClientObservable, + createSecuritySolutionStorageMock, + kibanaObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, + TestProviders, +} from '../../mock'; +import { createStore, State } from '../../store'; +import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import { act } from 'react-dom/test-utils'; +import { wait as waitFor } from '@testing-library/react'; -const updateSourceGroupIndicies = jest.fn(); -const mockManageSource = { - activeSourceGroupId: SecurityPageName.default, - availableIndexPatterns: mockPatterns, - availableSourceGroupIds: [SecurityPageName.default], - getManageSourceGroupById: jest.fn().mockReturnValue(mockSourceGroup(SecurityPageName.default)), - initializeSourceGroup: jest.fn(), - isIndexPatternsLoading: false, - setActiveSourceGroupId: jest.fn(), - updateSourceGroupIndicies, -}; -jest.mock('../../containers/sourcerer', () => { - const original = jest.requireActual('../../containers/sourcerer'); +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); return { ...original, - useManageSource: () => mockManageSource, + useDispatch: () => mockDispatch, }; }); const mockOptions = [ - { label: 'auditbeat-*', key: 'auditbeat-*-0', value: 'auditbeat-*', checked: 'on' }, - { label: 'endgame-*', key: 'endgame-*-1', value: 'endgame-*', checked: 'on' }, - { label: 'filebeat-*', key: 'filebeat-*-2', value: 'filebeat-*', checked: 'on' }, - { label: 'logs-*', key: 'logs-*-3', value: 'logs-*', checked: 'on' }, - { label: 'packetbeat-*', key: 'packetbeat-*-4', value: 'packetbeat-*', checked: undefined }, - { label: 'winlogbeat-*', key: 'winlogbeat-*-5', value: 'winlogbeat-*', checked: 'on' }, - { - label: 'apm-*-transaction*', - key: 'apm-*-transaction*-0', - value: 'apm-*-transaction*', - disabled: true, - checked: undefined, - }, - { - label: 'blobbeat-*', - key: 'blobbeat-*-1', - value: 'blobbeat-*', - disabled: true, - checked: undefined, - }, + { label: 'apm-*-transaction*', value: 'apm-*-transaction*' }, + { label: 'auditbeat-*', value: 'auditbeat-*' }, + { label: 'endgame-*', value: 'endgame-*' }, + { label: 'filebeat-*', value: 'filebeat-*' }, + { label: 'logs-*', value: 'logs-*' }, + { label: 'packetbeat-*', value: 'packetbeat-*' }, + { label: 'winlogbeat-*', value: 'winlogbeat-*' }, ]; +const defaultProps = { + scope: sourcererModel.SourcererScopeName.default, +}; describe('Sourcerer component', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + const state: State = mockGlobalState; + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore( + state, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage + ); + + beforeEach(() => { + store = createStore( + state, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage + ); + }); + // Using props callback instead of simulating clicks, // because EuiSelectable uses a virtualized list, which isn't easily testable via test subjects - it('Mounts with correct options selected and disabled', () => { - const wrapper = mount(); + it('Mounts with all options selected', () => { + const wrapper = mount( + + + + ); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - expect( - wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('options') + wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('selectedOptions') ).toEqual(mockOptions); }); - it('onChange calls updateSourceGroupIndicies', () => { - const wrapper = mount(); - wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - - const switcherOnChange = wrapper - .find(`[data-test-subj="indexPattern-switcher"]`) - .first() - .prop('onChange'); - // @ts-ignore - switcherOnChange([mockOptions[0], mockOptions[1]]); - expect(updateSourceGroupIndicies).toHaveBeenCalledWith(SecurityPageName.default, [ - mockOptions[0].value, - mockOptions[1].value, - ]); - }); - it('Disabled options have icon tooltip', () => { - const wrapper = mount(); - wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - // @ts-ignore - const Rendered = wrapper - .find(`[data-test-subj="indexPattern-switcher"]`) - .first() - .prop('renderOption')( - { - label: 'blobbeat-*', - key: 'blobbeat-*-1', - value: 'blobbeat-*', - disabled: true, - checked: undefined, + it('Mounts with some options selected', () => { + const state2 = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + loading: false, + selectedPatterns: [DEFAULT_INDEX_PATTERN[0]], + }, + }, }, - '' + }; + + store = createStore( + state2, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage + ); + const wrapper = mount( + + + ); - expect(Rendered.props.children[1].props.content).toEqual(i18n.DISABLED_INDEX_PATTERNS); + wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + expect( + wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('selectedOptions') + ).toEqual([mockOptions[0]]); }); - - it('Button links to index path', () => { - const wrapper = mount(); + it('onChange calls updateSourcererScopeIndices', async () => { + const wrapper = mount( + + + + ); + expect(true).toBeTruthy(); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - expect(wrapper.find(`[data-test-subj="add-index"]`).first().prop('href')).toEqual( - ADD_INDEX_PATH + await act(async () => { + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange([mockOptions[0], mockOptions[1]]); + await waitFor(() => { + wrapper.update(); + }); + }); + wrapper.find(`[data-test-subj="add-index"]`).first().simulate('click'); + + expect(mockDispatch).toHaveBeenCalledWith( + sourcererActions.setSelectedIndexPatterns({ + id: SourcererScopeName.default, + selectedPatterns: [mockOptions[0].value, mockOptions[1].value], + }) ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx index 6275ce19c3608..7a74f5bf2247f 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx @@ -4,50 +4,122 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo, useState } from 'react'; import { EuiButton, EuiButtonEmpty, - EuiHighlight, - EuiIconTip, + EuiComboBox, + EuiComboBoxOptionOption, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, EuiPopover, - EuiPopoverFooter, EuiPopoverTitle, - EuiSelectable, + EuiSpacer, + EuiText, + EuiToolTip, } from '@elastic/eui'; -import { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option'; -import { useManageSource } from '../../containers/sourcerer'; +import deepEqual from 'fast-deep-equal'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import styled from 'styled-components'; + import * as i18n from './translations'; import { SOURCERER_FEATURE_FLAG_ON } from '../../containers/sourcerer/constants'; -import { ADD_INDEX_PATH } from '../../../../common/constants'; - -export const MaybeSourcerer = React.memo(() => { - const { - activeSourceGroupId, - availableIndexPatterns, - getManageSourceGroupById, - isIndexPatternsLoading, - updateSourceGroupIndicies, - } = useManageSource(); - const { defaultPatterns, indexPatterns: selectedOptions, loading: loadingIndices } = useMemo( - () => getManageSourceGroupById(activeSourceGroupId), - [getManageSourceGroupById, activeSourceGroupId] +import { sourcererActions, sourcererModel } from '../../store/sourcerer'; +import { State } from '../../store'; +import { getSourcererScopeSelector, SourcererScopeSelector } from './selectors'; + +const PopoverContent = styled.div` + width: 600px; +`; + +const ResetButton = styled(EuiButtonEmpty)` + width: fit-content; +`; +interface SourcererComponentProps { + scope: sourcererModel.SourcererScopeName; +} + +export const SourcererComponent = React.memo(({ scope: scopeId }) => { + const dispatch = useDispatch(); + const sourcererScopeSelector = useMemo(getSourcererScopeSelector, []); + const { configIndexPatterns, kibanaIndexPatterns, sourcererScope } = useSelector< + State, + SourcererScopeSelector + >((state) => sourcererScopeSelector(state, scopeId), deepEqual); + const { selectedPatterns, loading } = sourcererScope; + const [isPopoverOpen, setPopoverIsOpen] = useState(false); + const [selectedOptions, setSelectedOptions] = useState>>( + selectedPatterns.map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })) ); - const loading = useMemo(() => loadingIndices || isIndexPatternsLoading, [ - isIndexPatternsLoading, - loadingIndices, - ]); + const setPopoverIsOpenCb = useCallback(() => setPopoverIsOpen((prevState) => !prevState), []); const onChangeIndexPattern = useCallback( - (newIndexPatterns: string[]) => { - updateSourceGroupIndicies(activeSourceGroupId, newIndexPatterns); + (newSelectedPatterns: string[]) => { + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: scopeId, + selectedPatterns: newSelectedPatterns, + }) + ); }, - [activeSourceGroupId, updateSourceGroupIndicies] + [dispatch, scopeId] + ); + + const renderOption = useCallback( + (option) => { + const { value } = option; + if (kibanaIndexPatterns.some((kip) => kip.title === value)) { + return ( + <> + {value} + + ); + } + return <>{value}; + }, + [kibanaIndexPatterns] + ); + + const onChangeCombo = useCallback((newSelectedOptions) => { + setSelectedOptions(newSelectedOptions); + }, []); + + const resetDataSources = useCallback(() => { + setSelectedOptions( + configIndexPatterns.map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })) + ); + }, [configIndexPatterns]); + + const handleSaveIndices = useCallback(() => { + onChangeIndexPattern(selectedOptions.map((so) => so.label)); + setPopoverIsOpen(false); + }, [onChangeIndexPattern, selectedOptions]); + + const handleClosePopOver = useCallback(() => { + setPopoverIsOpen(false); + }, []); + + const indexesPatternOptions = useMemo( + () => + [...configIndexPatterns, ...kibanaIndexPatterns.map((kip) => kip.title)].reduce< + Array> + >((acc, index) => { + if (index != null && !acc.some((o) => o.label.includes(index))) { + return [...acc, { label: index, value: index }]; + } + return acc; + }, []), + [configIndexPatterns, kibanaIndexPatterns] ); - const [isPopoverOpen, setPopoverIsOpen] = useState(false); - const setPopoverIsOpenCb = useCallback(() => setPopoverIsOpen((prevState) => !prevState), []); const trigger = useMemo( () => ( { data-test-subj="sourcerer-trigger" flush="left" iconSide="right" - iconType="indexSettings" + iconType="arrowDown" + isLoading={loading} onClick={setPopoverIsOpenCb} size="l" title={i18n.SOURCERER} @@ -63,99 +136,91 @@ export const MaybeSourcerer = React.memo(() => { {i18n.SOURCERER} ), - [setPopoverIsOpenCb] - ); - const options: EuiSelectableOption[] = useMemo( - () => - availableIndexPatterns.map((title, id) => ({ - label: title, - key: `${title}-${id}`, - value: title, - checked: selectedOptions.includes(title) ? 'on' : undefined, - })), - [availableIndexPatterns, selectedOptions] + [setPopoverIsOpenCb, loading] ); - const unSelectableOptions: EuiSelectableOption[] = useMemo( - () => - defaultPatterns - .filter((title) => !availableIndexPatterns.includes(title)) - .map((title, id) => ({ - label: title, - key: `${title}-${id}`, - value: title, - disabled: true, - checked: undefined, - })), - [availableIndexPatterns, defaultPatterns] - ); - const renderOption = useCallback( - (option, searchValue) => ( - <> - {option.label} - {option.disabled ? ( - - ) : null} - + + const comboBox = useMemo( + () => ( + ), - [] + [indexesPatternOptions, onChangeCombo, renderOption, selectedOptions] ); - const onChange = useCallback( - (choices: EuiSelectableOption[]) => { - const choice = choices.reduce( - (acc, { checked, label }) => (checked === 'on' ? [...acc, label] : acc), - [] - ); - onChangeIndexPattern(choice); - }, - [onChangeIndexPattern] + + useEffect(() => { + const newSelecteOptions = selectedPatterns.map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })); + setSelectedOptions((prevSelectedOptions) => { + if (!deepEqual(newSelecteOptions, prevSelectedOptions)) { + return newSelecteOptions; + } + return prevSelectedOptions; + }); + }, [selectedPatterns]); + + const tooltipContent = useMemo( + () => (isPopoverOpen ? null : sourcererScope.selectedPatterns.sort().join(', ')), + [isPopoverOpen, sourcererScope.selectedPatterns] ); - const allOptions = useMemo(() => [...options, ...unSelectableOptions], [ - options, - unSelectableOptions, - ]); + return ( - setPopoverIsOpen(false)} - display="block" - panelPaddingSize="s" - ownFocus - > -
- - <> - {i18n.CHANGE_INDEX_PATTERNS} - - - - - {(list, search) => ( - <> - {search} - {list} - - )} - - - - {i18n.ADD_INDEX_PATTERNS} - - -
-
+ + + + + <>{i18n.SELECT_INDEX_PATTERNS} + + + {i18n.INDEX_PATTERNS_SELECTION_LABEL} + + {comboBox} + + + + + {i18n.INDEX_PATTERNS_RESET} + + + + + {i18n.SAVE_INDEX_PATTERNS} + + + + + + ); }); -MaybeSourcerer.displayName = 'Sourcerer'; +SourcererComponent.displayName = 'Sourcerer'; -export const Sourcerer = SOURCERER_FEATURE_FLAG_ON ? MaybeSourcerer : () => null; +export const Sourcerer = SOURCERER_FEATURE_FLAG_ON ? SourcererComponent : () => null; diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx new file mode 100644 index 0000000000000..6bbe24e921880 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { State } from '../../store'; +import { sourcererSelectors } from '../../store/sourcerer'; +import { KibanaIndexPatterns, ManageScope, SourcererScopeName } from '../../store/sourcerer/model'; + +export interface SourcererScopeSelector { + configIndexPatterns: string[]; + kibanaIndexPatterns: KibanaIndexPatterns; + sourcererScope: ManageScope; +} + +export const getSourcererScopeSelector = () => { + const getKibanaIndexPatternsSelector = sourcererSelectors.kibanaIndexPatternsSelector(); + const getScopesSelector = sourcererSelectors.scopesSelector(); + const getConfigIndexPatternsSelector = sourcererSelectors.configIndexPatternsSelector(); + + const mapStateToProps = (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => { + const kibanaIndexPatterns = getKibanaIndexPatternsSelector(state); + const scope = getScopesSelector(state)[scopeId]; + const configIndexPatterns = getConfigIndexPatternsSelector(state); + + return { + kibanaIndexPatterns, + configIndexPatterns, + sourcererScope: scope, + }; + }; + + return mapStateToProps; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts b/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts index 71b1734dad6a6..473eb43d5c4fe 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts @@ -6,23 +6,26 @@ import { i18n } from '@kbn/i18n'; -export const SOURCERER = i18n.translate('xpack.securitySolution.indexPatterns.sourcerer', { - defaultMessage: 'Sourcerer', +export const SOURCERER = i18n.translate('xpack.securitySolution.indexPatterns.dataSourcesLabel', { + defaultMessage: 'Data sources', }); -export const CHANGE_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.help', { - defaultMessage: 'Change index patterns', +export const ALL_DEFAULT = i18n.translate('xpack.securitySolution.indexPatterns.allDefault', { + defaultMessage: 'All default', }); -export const ADD_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.add', { - defaultMessage: 'Configure Kibana index patterns', +export const SELECT_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.help', { + defaultMessage: 'Data sources selection', }); -export const CONFIGURE_INDEX_PATTERNS = i18n.translate( - 'xpack.securitySolution.indexPatterns.configure', +export const SAVE_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.save', { + defaultMessage: 'Save', +}); + +export const INDEX_PATTERNS_SELECTION_LABEL = i18n.translate( + 'xpack.securitySolution.indexPatterns.selectionLabel', { - defaultMessage: - 'Configure additional Kibana index patterns to see them become available in the Security Solution', + defaultMessage: 'Choose the source of the data on this page', } ); @@ -33,3 +36,17 @@ export const DISABLED_INDEX_PATTERNS = i18n.translate( 'Disabled index patterns are recommended on this page, but first need to be configured in your Kibana index pattern settings', } ); + +export const INDEX_PATTERNS_RESET = i18n.translate( + 'xpack.securitySolution.indexPatterns.resetButton', + { + defaultMessage: 'Reset', + } +); + +export const PICK_INDEX_PATTERNS = i18n.translate( + 'xpack.securitySolution.indexPatterns.pickIndexPatternsCombo', + { + defaultMessage: 'Pick index patterns', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts index b654eaf17b47b..79cbd87cda201 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EventType } from '../../../timelines/store/timeline/model'; +import { TimelineEventsType } from '../../../../common/types/timeline'; import * as i18n from './translations'; export interface TopNOption { inputDisplay: string; - value: EventType; + value: TimelineEventsType; 'data-test-subj': string; } @@ -52,8 +52,8 @@ export const defaultOptions = [...rawEvents, ...alertEvents]; * is always in sync with the `EventType` chosen by the user in * the active timeline. */ -export const getOptions = (activeTimelineEventType?: EventType): TopNOption[] => { - switch (activeTimelineEventType) { +export const getOptions = (activeTimelineEventsType?: TimelineEventsType): TopNOption[] => { + switch (activeTimelineEventsType) { case 'all': return allEvents; case 'raw': diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx index 31318122eb564..594bffbd4ff63 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx @@ -168,6 +168,17 @@ const store = createStore( storage ); +let testProps = { + browserFields: mockBrowserFields, + field, + indexNames: [], + indexPattern: mockIndexPattern, + timelineId: TimelineId.hostsPageExternalAlerts, + toggleTopN: jest.fn(), + onFilterAdded: jest.fn(), + value, +}; + describe('StatefulTopN', () => { // Suppress warnings about "react-beautiful-dnd" /* eslint-disable no-console */ @@ -189,16 +200,7 @@ describe('StatefulTopN', () => { wrapper = mount( - + ); @@ -277,19 +279,14 @@ describe('StatefulTopN', () => { filterManager, }, }; + testProps = { + ...testProps, + timelineId: TimelineId.active, + }; wrapper = mount( - + ); @@ -345,37 +342,33 @@ describe('StatefulTopN', () => { expect(props.to).toEqual('2020-04-15T03:46:09.047Z'); }); }); + describe('rendering in a NON-active timeline context', () => { + test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, () => { + const filterManager = new FilterManager(mockUiSettingsForFilterManager); - test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, () => { - const filterManager = new FilterManager(mockUiSettingsForFilterManager); + const manageTimelineForTesting = { + [TimelineId.active]: { + ...getTimelineDefaults(TimelineId.active), + filterManager, + documentType: 'alerts', + }, + }; - const manageTimelineForTesting = { - [TimelineId.active]: { - ...getTimelineDefaults(TimelineId.active), - filterManager, - documentType: 'alerts', - }, - }; - - const wrapper = mount( - - - - - - ); - - const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props; - - expect(props.defaultView).toEqual('alert'); + testProps = { + ...testProps, + timelineId: TimelineId.detectionsPage, + }; + const wrapper = mount( + + + + + + ); + + const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props; + + expect(props.defaultView).toEqual('alert'); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx index d71242329bcda..9c81cb57335a5 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx @@ -74,7 +74,7 @@ interface OwnProps { browserFields: BrowserFields; field: string; indexPattern: IIndexPattern; - indexToAdd: string[] | null; + indexNames: string[]; timelineId?: string; toggleTopN: () => void; onFilterAdded?: () => void; @@ -93,7 +93,7 @@ const StatefulTopNComponent: React.FC = ({ dataProviders, field, indexPattern, - indexToAdd, + indexNames, globalFilters = EMPTY_FILTERS, globalQuery = EMPTY_QUERY, kqlMode, @@ -109,7 +109,6 @@ const StatefulTopNComponent: React.FC = ({ const options = getOptions( timelineId === TimelineId.active ? activeTimelineEventType : undefined ); - return ( = ({ filters={timelineId === TimelineId.active ? EMPTY_FILTERS : globalFilters} from={timelineId === TimelineId.active ? activeTimelineFrom : from} indexPattern={indexPattern} - indexToAdd={indexToAdd} + indexNames={indexNames} options={options} query={timelineId === TimelineId.active ? EMPTY_QUERY : globalQuery} setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker} diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx index 667d1816e8f07..829f918ddfe1b 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx @@ -13,6 +13,8 @@ import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { allEvents, defaultOptions } from './helpers'; import { TopN } from './top_n'; +import { TimelineEventsType } from '../../../../common/types/timeline'; +import { InputsModelId } from '../../store/inputs/constants'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -103,29 +105,34 @@ describe('TopN', () => { const query = { query: '', language: 'kuery' }; + const toggleTopN = jest.fn(); + const eventTypes: { [id: string]: TimelineEventsType } = { + raw: 'raw', + alert: 'alert', + all: 'all', + }; + let testProps = { + defaultView: eventTypes.raw, + field, + filters: [], + from: '2020-04-14T00:31:47.695Z', + indexNames: [], + indexPattern: mockIndexPattern, + options: defaultOptions, + query, + setAbsoluteRangeDatePicker, + setAbsoluteRangeDatePickerTarget: 'global' as InputsModelId, + setQuery: jest.fn(), + to: '2020-04-15T00:31:47.695Z', + toggleTopN, + value, + }; describe('common functionality', () => { - let toggleTopN: () => void; let wrapper: ReactWrapper; - beforeEach(() => { - toggleTopN = jest.fn(); wrapper = mount( - + ); }); @@ -143,28 +150,12 @@ describe('TopN', () => { }); describe('events view', () => { - let toggleTopN: () => void; let wrapper: ReactWrapper; beforeEach(() => { - toggleTopN = jest.fn(); wrapper = mount( - + ); }); @@ -181,37 +172,25 @@ describe('TopN', () => { }); describe('alerts view', () => { - let toggleTopN: () => void; let wrapper: ReactWrapper; beforeEach(() => { - toggleTopN = jest.fn(); + testProps = { + ...testProps, + defaultView: eventTypes.alert, + }; wrapper = mount( - + ); }); - test(`it renders SignalsByCategory when defaultView is 'signal'`, () => { + test(`it renders SignalsByCategory when defaultView is 'alert'`, () => { expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(true); }); - test(`it does NOT render EventsByDataset when defaultView is 'signal'`, () => { + test(`it does NOT render EventsByDataset when defaultView is 'alert'`, () => { expect( wrapper.find('[data-test-subj="eventsByDatasetOverview-uuid.v4()Panel"]').exists() ).toBe(false); @@ -222,24 +201,14 @@ describe('TopN', () => { let wrapper: ReactWrapper; beforeEach(() => { + testProps = { + ...testProps, + defaultView: eventTypes.all, + options: allEvents, + }; wrapper = mount( - + ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx index 064241a7216f4..4f0a71dcc3ebb 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx @@ -14,7 +14,7 @@ import { EventsByDataset } from '../../../overview/components/events_by_dataset' import { SignalsByCategory } from '../../../overview/components/signals_by_category'; import { Filter, IIndexPattern, Query } from '../../../../../../../src/plugins/data/public'; import { InputsModelId } from '../../store/inputs/constants'; -import { EventType } from '../../../timelines/store/timeline/model'; +import { TimelineEventsType } from '../../../../common/types/timeline'; import { TopNOption } from './helpers'; import * as i18n from './translations'; @@ -45,11 +45,11 @@ const TopNContent = styled.div` export interface Props extends Pick { combinedQueries?: string; - defaultView: EventType; + defaultView: TimelineEventsType; field: string; filters: Filter[]; indexPattern: IIndexPattern; - indexToAdd?: string[] | null; + indexNames: string[]; options: TopNOption[]; query: Query; setAbsoluteRangeDatePicker: ActionCreator<{ @@ -75,7 +75,7 @@ const TopNComponent: React.FC = ({ field, from, indexPattern, - indexToAdd, + indexNames, options, query = DEFAULT_QUERY, setAbsoluteRangeDatePicker, @@ -85,8 +85,10 @@ const TopNComponent: React.FC = ({ to, toggleTopN, }) => { - const [view, setView] = useState(defaultView); - const onViewSelected = useCallback((value: string) => setView(value as EventType), [setView]); + const [view, setView] = useState(defaultView); + const onViewSelected = useCallback((value: string) => setView(value as TimelineEventsType), [ + setView, + ]); useEffect(() => { setView(defaultView); @@ -123,7 +125,7 @@ const TopNComponent: React.FC = ({ from={from} headerChildren={headerChildren} indexPattern={indexPattern} - indexToAdd={indexToAdd} + indexNames={indexNames} onlyField={field} query={query} setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget} diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/translations.ts b/x-pack/plugins/security_solution/public/common/components/top_n/translations.ts index b149a5eb1458f..76bb3dcfa6d37 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/top_n/translations.ts @@ -19,5 +19,5 @@ export const RAW_EVENTS = i18n.translate('xpack.securitySolution.topN.rawEventsS }); export const ALERT_EVENTS = i18n.translate('xpack.securitySolution.topN.alertEventsSelectLabel', { - defaultMessage: 'Alert events', + defaultMessage: 'Detection Alerts', }); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts index 5a4aec93dd9aa..e5c09d229808b 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts @@ -17,6 +17,7 @@ export enum CONSTANTS { networkPage = 'network.page', overviewPage = 'overview.page', savedQuery = 'savedQuery', + sourcerer = 'sourcerer', timeline = 'timeline', timelinePage = 'timeline.page', timerange = 'timerange', diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts index 6052913b4183b..05000f91f094c 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts @@ -22,6 +22,8 @@ import { formatDate } from '../super_date_picker'; import { NavTab } from '../navigation/types'; import { CONSTANTS, UrlStateType } from './constants'; import { ReplaceStateInLocation, UpdateUrlStateString } from './types'; +import { sourcererSelectors } from '../../store/sourcerer'; +import { SourcererScopeName, SourcererScopePatterns } from '../../store/sourcerer/model'; export const decodeRisonUrlState = (value: string | undefined): T | null => { try { @@ -58,16 +60,14 @@ export const replaceStateKeyInQueryString = (stateKey: string, urlState: T) = // ಠ_ಠ Code was copied from x-pack/legacy/plugins/infra/public/utils/url_state.tsx ಠ_ಠ // Remove this if these utilities are promoted to kibana core - const encodedUrlState = - typeof urlState !== 'undefined' ? encodeRisonUrlState(urlState) : undefined; - - return stringify( - url.encodeQuery({ - ...previousQueryValues, - [stateKey]: encodedUrlState, - }), - { sort: false, encode: false } - ); + const newValue = + typeof urlState === 'undefined' + ? previousQueryValues + : { + ...previousQueryValues, + [stateKey]: encodeRisonUrlState(urlState), + }; + return stringify(url.encodeQuery(newValue), { sort: false, encode: false }); }; export const replaceQueryStringInLocation = ( @@ -118,6 +118,7 @@ export const makeMapStateToProps = () => { const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); const getGlobalSavedQuerySelector = inputsSelectors.globalSavedQuerySelector(); const getTimeline = timelineSelectors.getTimelineByIdSelector(); + const getSourcererScopes = sourcererSelectors.scopesSelector(); const mapStateToProps = (state: State) => { const inputState = getInputsSelector(state); const { linkTo: globalLinkTo, timerange: globalTimerange } = inputState.global; @@ -147,10 +148,16 @@ export const makeMapStateToProps = () => { [CONSTANTS.savedQuery]: savedQuery.id, }; } + const sourcerer = getSourcererScopes(state); + const activeScopes: SourcererScopeName[] = Object.keys(sourcerer) as SourcererScopeName[]; + const selectedPatterns: SourcererScopePatterns = activeScopes + .filter((scope) => scope === SourcererScopeName.default) + .reduce((acc, scope) => ({ ...acc, [scope]: sourcerer[scope]?.selectedPatterns }), {}); return { urlState: { ...searchAttr, + [CONSTANTS.sourcerer]: selectedPatterns, [CONSTANTS.timerange]: { global: { [CONSTANTS.timerange]: globalTimerange, @@ -217,6 +224,17 @@ export const updateUrlStateString = ({ urlStateKey: urlKey, }); } + } else if (urlKey === CONSTANTS.sourcerer) { + const sourcererState = decodeRisonUrlState(newUrlStateString); + if (sourcererState != null && Object.keys(sourcererState).length > 0) { + return replaceStateInLocation({ + history, + pathName, + search, + urlStateToReplace: sourcererState, + urlStateKey: urlKey, + }); + } } else if (urlKey === CONSTANTS.filters) { const queryState = decodeRisonUrlState(newUrlStateString); if (isEmpty(queryState)) { diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx index 72df9d613abac..fc970c066e8a5 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx @@ -161,7 +161,7 @@ describe('UrlStateContainer', () => { ).toEqual({ hash: '', pathname: examplePath, - search: `?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + search: `?query=(language:kuery,query:'host.name:%22siem-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, state: '', }); } diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx index 723f2d235864f..9e845ec538aa0 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx @@ -83,7 +83,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => hash: '', pathname: '/network', search: - "?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "?query=(language:kuery,query:'host.name:%22siem-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", state: '', }); }); @@ -114,7 +114,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => hash: '', pathname: '/network', search: - "?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "?query=(language:kuery,query:'host.name:%22siem-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", state: '', }); }); @@ -147,7 +147,40 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => hash: '', pathname: '/network', search: - "?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))&timeline=(id:hello_timeline_id,isOpen:!t)", + "?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))&timeline=(id:hello_timeline_id,isOpen:!t)", + state: '', + }); + }); + + test('sourcerer redux state updates the url', () => { + mockProps = getMockPropsObj({ + page: CONSTANTS.networkPage, + examplePath: '/network', + namespaceLower: 'network', + pageName: SecurityPageName.network, + detailName: undefined, + }).noSearch.undefinedQuery; + + const wrapper = mount( + useUrlStateHooks(args)} /> + ); + const newUrlState = { + ...mockProps.urlState, + sourcerer: ['cool', 'patterns'], + }; + + wrapper.setProps({ + hookProps: { ...mockProps, urlState: newUrlState, isInitializing: false }, + }); + wrapper.update(); + + expect( + mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0] + ).toStrictEqual({ + hash: '', + pathname: '/network', + search: + "?sourcerer=!(cool,patterns)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", state: '', }); }); @@ -176,7 +209,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => hash: '', pathname: examplePath, search: - "?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", state: '', }); } @@ -204,7 +237,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => expect( mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0].search ).toEqual( - "?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))" + "?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))" ); wrapper.setProps({ hookProps: updatedProps }); @@ -213,7 +246,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => expect( mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0].search ).toEqual( - "?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))" + "?query=(language:kuery,query:'host.name:%22siem-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))" ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx index 6eccf52ec72da..1e77ae7766630 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx @@ -8,7 +8,7 @@ import { get, isEmpty } from 'lodash/fp'; import { Dispatch } from 'redux'; import { Query, Filter } from '../../../../../../../src/plugins/data/public'; -import { inputsActions } from '../../store/actions'; +import { inputsActions, sourcererActions } from '../../store/actions'; import { InputsModelId, TimeRangeKinds } from '../../store/inputs/constants'; import { UrlInputsModel, @@ -22,6 +22,8 @@ import { decodeRisonUrlState } from './helpers'; import { normalizeTimeRange } from './normalize_time_range'; import { DispatchSetInitialStateFromUrl, SetInitialStateFromUrl } from './types'; import { queryTimelineById } from '../../../timelines/components/open_timeline/helpers'; +import { SourcererScopeName, SourcererScopePatterns } from '../../store/sourcerer/model'; +import { SecurityPageName } from '../../../../common/constants'; export const dispatchSetInitialStateFromUrl = ( dispatch: Dispatch @@ -40,6 +42,22 @@ export const dispatchSetInitialStateFromUrl = ( if (urlKey === CONSTANTS.timerange) { updateTimerange(newUrlStateString, dispatch); } + if (urlKey === CONSTANTS.sourcerer) { + const sourcererState = decodeRisonUrlState(newUrlStateString); + if (sourcererState != null) { + const activeScopes: SourcererScopeName[] = Object.keys(sourcererState).filter( + (key) => !(key === SourcererScopeName.default && pageName === SecurityPageName.detections) + ) as SourcererScopeName[]; + activeScopes.forEach((scope) => + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: scope, + selectedPatterns: sourcererState[scope] ?? [], + }) + ) + ); + } + } if (urlKey === CONSTANTS.appQuery && indexPattern != null) { const appQuery = decodeRisonUrlState(newUrlStateString); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts index 8d471e843320c..6f04226fa3a19 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts @@ -117,6 +117,7 @@ export const defaultProps: UrlStateContainerPropTypes = { id: '', isOpen: false, }, + [CONSTANTS.sourcerer]: {}, }, setInitialStateFromUrl: dispatchSetInitialStateFromUrl(mockDispatch), updateTimeline: (jest.fn() as unknown) as DispatchUpdateTimeline, diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts index f383e18132385..301771a4db6b9 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts @@ -22,11 +22,13 @@ import { DispatchUpdateTimeline } from '../../../timelines/components/open_timel import { NavTab } from '../navigation/types'; import { CONSTANTS, UrlStateType } from './constants'; +import { SourcererScopePatterns } from '../../store/sourcerer/model'; export const ALL_URL_STATE_KEYS: KeyUrlState[] = [ CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ]; @@ -36,6 +38,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -43,6 +46,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -51,6 +55,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -58,6 +63,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -65,6 +71,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -72,6 +79,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -93,6 +101,7 @@ export interface UrlState { [CONSTANTS.appQuery]?: Query; [CONSTANTS.filters]?: Filter[]; [CONSTANTS.savedQuery]?: string; + [CONSTANTS.sourcerer]: SourcererScopePatterns; [CONSTANTS.timerange]: UrlInputsModel; [CONSTANTS.timeline]: TimelineUrl; } diff --git a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx index f3136b0a40b3e..0908c887d25f6 100644 --- a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx @@ -28,6 +28,9 @@ const Wrapper = styled.div` &.siemWrapperPage--fullHeight { height: 100%; + display: flex; + flex-direction: column; + flex: 1 1 auto; } &.siemWrapperPage--withTimeline { @@ -36,6 +39,9 @@ const Wrapper = styled.div` &.siemWrapperPage--noPadding { padding: 0; + display: flex; + flex-direction: column; + flex: 1 1 auto; } `; diff --git a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx index f6ebbb990f223..489ccb23c9b2c 100644 --- a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx @@ -29,6 +29,7 @@ const AnomaliesQueryTabBodyComponent: React.FC = ({ AnomaliesTableComponent, flowTarget, ip, + indexNames, }) => { const { jobs } = useInstalledSecurityJobs(); const [anomalyScore] = useUiSetting$(DEFAULT_ANOMALY_SCORE); @@ -57,6 +58,7 @@ const AnomaliesQueryTabBodyComponent: React.FC = ({ endDate={endDate} filterQuery={mergedFilterQuery} id={ID} + indexNames={indexNames} setQuery={setQuery} startDate={startDate} {...histogramConfigs} diff --git a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts index d716df70246f7..3ce4b8b6d4494 100644 --- a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts +++ b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts @@ -24,6 +24,7 @@ export type AnomaliesQueryTabBodyProps = QueryTabBodyProps & { deleteQuery?: ({ id }: { id: string }) => void; endDate: GlobalTimeArgs['to']; flowTarget?: FlowTarget; + indexNames: string[]; narrowDateRange: NarrowDateRange; setQuery: GlobalTimeArgs['setQuery']; startDate: GlobalTimeArgs['from']; diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts index d70762615818b..dc2d6605bc292 100644 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts @@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { useKibana } from '../../../../common/lib/kibana'; import { @@ -18,11 +17,15 @@ import { LastTimeDetails, LastEventIndexKey, } from '../../../../../common/search_strategy/timeline'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; -import { useWithSource } from '../../source'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; +import { DocValueFields } from '../../../../../common/search_strategy'; -// const ID = 'timelineEventsLastEventTimeQuery'; +const ID = 'timelineEventsLastEventTimeQuery'; export interface UseTimelineLastEventTimeArgs { lastSeen: string | null; @@ -31,26 +34,29 @@ export interface UseTimelineLastEventTimeArgs { } interface UseTimelineLastEventTimeProps { + docValueFields: DocValueFields[]; indexKey: LastEventIndexKey; + indexNames: string[]; details: LastTimeDetails; } export const useTimelineLastEventTime = ({ + docValueFields, indexKey, + indexNames, details, }: UseTimelineLastEventTimeProps): [boolean, UseTimelineLastEventTimeArgs] => { - const { data, notifications, uiSettings } = useKibana().services; - const { docValueFields } = useWithSource('default'); + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [TimelineLastEventTimeRequest, setTimelineLastEventTimeRequest] = useState< TimelineEventsLastEventTimeRequestOptions >({ - defaultIndex, - factoryQueryType: TimelineEventsQueries.lastEventTime, + defaultIndex: indexNames, docValueFields, + factoryQueryType: TimelineEventsQueries.lastEventTime, + id: ID, indexKey, details, }); @@ -80,7 +86,7 @@ export const useTimelineLastEventTime = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setTimelineLastEventTimeResponse((prevResponse) => ({ @@ -91,7 +97,7 @@ export const useTimelineLastEventTime = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -129,7 +135,8 @@ export const useTimelineLastEventTime = ({ setTimelineLastEventTimeRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, + docValueFields, indexKey, details, }; @@ -138,7 +145,7 @@ export const useTimelineLastEventTime = ({ } return prevRequest; }); - }, [defaultIndex, details, indexKey]); + }, [indexNames, details, docValueFields, indexKey]); useEffect(() => { timelineLastEventTimeSearch(TimelineLastEventTimeRequest); diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/last_event_time.gql_query.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/last_event_time.gql_query.ts deleted file mode 100644 index 36305ef0dc882..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/last_event_time.gql_query.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; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const LastEventTimeGqlQuery = gql` - query GetLastEventTimeQuery( - $sourceId: ID! - $indexKey: LastEventIndexKey! - $details: LastTimeDetails! - $defaultIndex: [String!]! - $docValueFields: [docValueFieldsInput!]! - ) { - source(id: $sourceId) { - id - LastEventTime( - indexKey: $indexKey - details: $details - defaultIndex: $defaultIndex - docValueFields: $docValueFields - ) { - lastSeen - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/mock.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/mock.ts index bdeb1db4e1b28..208c03b453e04 100644 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/mock.ts +++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/mock.ts @@ -4,28 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; -import { GetLastEventTimeQuery, LastEventIndexKey } from '../../../../graphql/types'; - -import { LastEventTimeGqlQuery } from './last_event_time.gql_query'; - interface MockLastEventTimeQuery { - request: { - query: GetLastEventTimeQuery.Query; - variables: GetLastEventTimeQuery.Variables; - }; - result: { - data?: { - source: { - id: string; - LastEventTime: { - lastSeen: string | null; - errorMessage: string | null; - }; - }; - }; - errors?: [{ message: string }]; - }; + lastSeen: string | null; + errorMessage: string | null; } const getTimeTwelveMinutesAgo = () => { @@ -35,28 +16,7 @@ const getTimeTwelveMinutesAgo = () => { return new Date(twelveMinutes).toISOString(); }; -export const mockLastEventTimeQuery: MockLastEventTimeQuery[] = [ - { - request: { - query: LastEventTimeGqlQuery, - variables: { - sourceId: 'default', - indexKey: LastEventIndexKey.hosts, - details: {}, - defaultIndex: DEFAULT_INDEX_PATTERN, - docValueFields: [], - }, - }, - result: { - data: { - source: { - id: 'default', - LastEventTime: { - lastSeen: getTimeTwelveMinutesAgo(), - errorMessage: null, - }, - }, - }, - }, - }, -]; +export const mockLastEventTimeQuery: MockLastEventTimeQuery = { + lastSeen: getTimeTwelveMinutesAgo(), + errorMessage: null, +}; diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.gql_query.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.gql_query.ts deleted file mode 100644 index 6fb729ca7e9a0..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.gql_query.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const MatrixHistogramGqlQuery = gql` - query GetMatrixHistogramQuery( - $defaultIndex: [String!]! - $filterQuery: String - $histogramType: HistogramType! - $inspect: Boolean! - $sourceId: ID! - $stackByField: String! - $timerange: TimerangeInput! - ) { - source(id: $sourceId) { - id - MatrixHistogram( - timerange: $timerange - filterQuery: $filterQuery - defaultIndex: $defaultIndex - stackByField: $stackByField - histogramType: $histogramType - ) { - matrixHistogramData { - x - y - g - } - totalCount - inspect @include(if: $inspect) { - dsl - response - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index 65ad3cc994c67..ca8bcc637717b 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -5,21 +5,24 @@ */ import deepEqual from 'fast-deep-equal'; -import { isEmpty, noop } from 'lodash/fp'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { MatrixHistogramQueryProps } from '../../components/matrix_histogram/types'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel } from '../../../common/store'; import { createFilter } from '../../../common/containers/helpers'; -import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; import { MatrixHistogramQuery, MatrixHistogramRequestOptions, MatrixHistogramStrategyResponse, MatrixHistogramData, } from '../../../../common/search_strategy/security_solution'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isErrorResponse, + isCompleteResponse, +} from '../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; @@ -36,25 +39,18 @@ export const useMatrixHistogram = ({ errorMessage, filterQuery, histogramType, - indexToAdd, + indexNames, stackByField, startDate, }: MatrixHistogramQueryProps): [boolean, UseMatrixHistogramArgs] => { const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const [configIndex] = useUiSetting$(DEFAULT_INDEX_KEY); - const defaultIndex = useMemo(() => { - if (indexToAdd != null && !isEmpty(indexToAdd)) { - return [...configIndex, ...indexToAdd]; - } - return configIndex; - }, [configIndex, indexToAdd]); const [loading, setLoading] = useState(false); const [matrixHistogramRequest, setMatrixHistogramRequest] = useState< MatrixHistogramRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: MatrixHistogramQuery, filterQuery: createFilter(filterQuery), histogramType, @@ -90,7 +86,7 @@ export const useMatrixHistogram = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setMatrixHistogramResponse((prevResponse) => ({ @@ -102,7 +98,7 @@ export const useMatrixHistogram = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -136,7 +132,7 @@ export const useMatrixHistogram = ({ setMatrixHistogramRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -149,7 +145,7 @@ export const useMatrixHistogram = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, startDate]); + }, [indexNames, endDate, filterQuery, startDate]); useEffect(() => { hostsSearch(matrixHistogramRequest); diff --git a/x-pack/plugins/security_solution/public/common/containers/query_template.tsx b/x-pack/plugins/security_solution/public/common/containers/query_template.tsx index eaa43c255a944..80791d91481a8 100644 --- a/x-pack/plugins/security_solution/public/common/containers/query_template.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/query_template.tsx @@ -14,6 +14,7 @@ import { DocValueFields } from './source'; export { DocValueFields }; export interface QueryTemplateProps { + indexNames: string[]; docValueFields?: DocValueFields[]; id?: string; endDate?: string; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts b/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts deleted file mode 100644 index 630515c5cbed4..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts +++ /dev/null @@ -1,31 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const sourceQuery = gql` - query SourceQuery($sourceId: ID = "default", $defaultIndex: [String!]!) { - source(id: $sourceId) { - id - status { - indicesExist(defaultIndex: $defaultIndex) - indexFields(defaultIndex: $defaultIndex) { - category - description - example - indexes - name - searchable - type - aggregatable - format - esTypes - subType - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx deleted file mode 100644 index 8ba7f7da7b8e3..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { act, renderHook } from '@testing-library/react-hooks'; - -import { useWithSource, indicesExistOrDataTemporarilyUnavailable } from '.'; -import { NO_ALERT_INDEX } from '../../../../common/constants'; -import { mockBrowserFields, mockIndexFields, mocksSource } from './mock'; - -jest.mock('../../lib/kibana'); -jest.mock('../../utils/apollo_context', () => ({ - useApolloClient: jest.fn().mockReturnValue({ - query: jest.fn().mockImplementation(() => Promise.resolve(mocksSource[0].result)), - }), -})); - -describe('Index Fields & Browser Fields', () => { - test('At initialization the value of indicesExists should be true', async () => { - const { result, waitForNextUpdate } = renderHook(() => useWithSource()); - const initialResult = result.current; - - await waitForNextUpdate(); - - return expect(initialResult).toEqual({ - browserFields: {}, - docValueFields: [], - errorMessage: null, - indexPattern: { - fields: [], - title: - 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*', - }, - indicesExist: true, - loading: true, - }); - }); - - test('returns memoized value', async () => { - const { result, waitForNextUpdate, rerender } = renderHook(() => useWithSource()); - await waitForNextUpdate(); - - const result1 = result.current; - act(() => rerender()); - const result2 = result.current; - - return expect(result1).toBe(result2); - }); - - test('Index Fields', async () => { - const { result, waitForNextUpdate } = renderHook(() => useWithSource()); - - await waitForNextUpdate(); - - return expect(result).toEqual({ - current: { - indicesExist: true, - browserFields: mockBrowserFields, - docValueFields: [ - { - field: '@timestamp', - format: 'date_time', - }, - { - field: 'event.end', - format: 'date_time', - }, - ], - indexPattern: { - fields: mockIndexFields, - title: - 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*', - }, - loading: false, - errorMessage: null, - }, - error: undefined, - }); - }); - - test('Make sure we are not querying for NO_ALERT_INDEX and it is not includes in the index pattern', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useWithSource('default', [NO_ALERT_INDEX]) - ); - - await waitForNextUpdate(); - return expect(result.current.indexPattern.title).toEqual( - 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*' - ); - }); - - describe('indicesExistOrDataTemporarilyUnavailable', () => { - test('it returns true when undefined', () => { - let undefVar; - const result = indicesExistOrDataTemporarilyUnavailable(undefVar); - expect(result).toBeTruthy(); - }); - test('it returns true when true', () => { - const result = indicesExistOrDataTemporarilyUnavailable(true); - expect(result).toBeTruthy(); - }); - test('it returns false when false', () => { - const result = indicesExistOrDataTemporarilyUnavailable(false); - expect(result).toBeFalsy(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index ffbecf9e3d433..4b1db8a2871bd 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -4,42 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isUndefined } from 'lodash'; import { set } from '@elastic/safer-lodash-set/fp'; -import { get, keyBy, pick, isEmpty } from 'lodash/fp'; -import { useEffect, useMemo, useState } from 'react'; +import { keyBy, pick, isEmpty, isEqual, isUndefined } from 'lodash/fp'; import memoizeOne from 'memoize-one'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { IIndexPattern } from 'src/plugins/data/public'; -import { DEFAULT_INDEX_KEY, NO_ALERT_INDEX } from '../../../../common/constants'; -import { useUiSetting$ } from '../../lib/kibana'; +import { useKibana } from '../../lib/kibana'; +import { + IndexField, + IndexFieldsStrategyResponse, + IndexFieldsStrategyRequest, + BrowserField, + BrowserFields, +} from '../../../../common/search_strategy/index_fields'; +import { AbortError } from '../../../../../../../src/plugins/data/common'; +import * as i18n from './translations'; +import { SourcererScopeName } from '../../store/sourcerer/model'; +import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; -import { IndexField, SourceQuery } from '../../../graphql/types'; +import { State } from '../../store'; +import { DocValueFields } from '../../../../common/search_strategy/common'; -import { sourceQuery } from './index.gql_query'; -import { useApolloClient } from '../../utils/apollo_context'; - -export { sourceQuery }; - -export interface BrowserField { - aggregatable: boolean; - category: string; - description: string | null; - example: string | number | null; - fields: Readonly>>; - format: string; - indexes: string[]; - name: string; - searchable: boolean; - type: string; -} - -export interface DocValueFields { - field: string; - format: string; -} - -export type BrowserFields = Readonly>>; +export { BrowserField, BrowserFields, DocValueFields }; export const getAllBrowserFields = (browserFields: BrowserFields): Array> => Object.values(browserFields).reduce>>( @@ -85,14 +73,12 @@ export const getDocValueFields = memoizeOne( (_title: string, fields: IndexField[]): DocValueFields[] => fields && fields.length > 0 ? fields.reduce((accumulator: DocValueFields[], field: IndexField) => { - if (field.type === 'date' && accumulator.length < 100) { - const format: string = - field.format != null && !isEmpty(field.format) ? field.format : 'date_time'; + if (field.readFromDocValues && accumulator.length < 100) { return [ ...accumulator, { field: field.name, - format, + format: field.format, }, ]; } @@ -107,115 +93,196 @@ export const indicesExistOrDataTemporarilyUnavailable = ( indicesExist: boolean | null | undefined ) => indicesExist || isUndefined(indicesExist); -const EMPTY_BROWSER_FIELDS = {}; -const EMPTY_DOCVALUE_FIELD: DocValueFields[] = []; +const DEFAULT_BROWSER_FIELDS = {}; +const DEFAULT_INDEX_PATTERNS = { fields: [], title: '' }; +const DEFAULT_DOC_VALUE_FIELDS: DocValueFields[] = []; -interface UseWithSourceState { +interface FetchIndexReturn { browserFields: BrowserFields; docValueFields: DocValueFields[]; - errorMessage: string | null; - indexPattern: IIndexPattern; - indicesExist: boolean | undefined | null; - loading: boolean; + indexes: string[]; + indexExists: boolean; + indexPatterns: IIndexPattern; } -export const useWithSource = ( - sourceId = 'default', - indexToAdd?: string[] | null, - onlyCheckIndexToAdd?: boolean, - // Fun fact: When using this hook multiple times within a component (e.g. add_exception_modal & edit_exception_modal), - // the apolloClient will perform queryDeduplication and prevent the first query from executing. A deep compare is not - // performed on `indices`, so another field must be passed to circumvent this. - // For details, see https://github.com/apollographql/react-apollo/issues/2202 - queryDeduplication = 'default' -) => { - const [configIndex] = useUiSetting$(DEFAULT_INDEX_KEY); - const defaultIndex = useMemo(() => { - const filterIndexAdd = (indexToAdd ?? []).filter((item) => item !== NO_ALERT_INDEX); - if (!isEmpty(filterIndexAdd)) { - return onlyCheckIndexToAdd ? filterIndexAdd : [...configIndex, ...filterIndexAdd]; - } - return configIndex; - }, [configIndex, indexToAdd, onlyCheckIndexToAdd]); - - const [state, setState] = useState({ - browserFields: EMPTY_BROWSER_FIELDS, - docValueFields: EMPTY_DOCVALUE_FIELD, - errorMessage: null, - indexPattern: getIndexFields(defaultIndex.join(), []), - indicesExist: indicesExistOrDataTemporarilyUnavailable(undefined), - loading: true, +export const useFetchIndex = ( + indexNames: string[], + onlyCheckIfIndicesExist: boolean = false +): [boolean, FetchIndexReturn] => { + const { data, notifications } = useKibana().services; + const abortCtrl = useRef(new AbortController()); + const previousIndexesName = useRef([]); + const [isLoading, setLoading] = useState(true); + + const [state, setState] = useState({ + browserFields: DEFAULT_BROWSER_FIELDS, + docValueFields: DEFAULT_DOC_VALUE_FIELDS, + indexes: indexNames, + indexExists: true, + indexPatterns: DEFAULT_INDEX_PATTERNS, }); - const apolloClient = useApolloClient(); + const indexFieldsSearch = useCallback( + (iNames) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + const searchSubscription$ = data.search + .search( + { indices: iNames, onlyCheckIfIndicesExist }, + { + abortSignal: abortCtrl.current.signal, + strategy: 'securitySolutionIndexFields', + } + ) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + const stringifyIndices = response.indicesExist.sort().join(); + previousIndexesName.current = response.indicesExist; + setLoading(false); + setState({ + browserFields: getBrowserFields(stringifyIndices, response.indexFields), + docValueFields: getDocValueFields(stringifyIndices, response.indexFields), + indexes: response.indicesExist, + indexExists: response.indicesExist.length > 0, + indexPatterns: getIndexFields(stringifyIndices, response.indexFields), + }); + } + searchSubscription$.unsubscribe(); + } else if (!didCancel && response.isPartial && !response.isRunning) { + setLoading(false); + notifications.toasts.addWarning(i18n.ERROR_BEAT_FIELDS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!didCancel) { + setLoading(false); + } - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - - async function fetchSource() { - if (!apolloClient) return; - - setState((prevState) => ({ ...prevState, loading: true })); - - try { - const result = await apolloClient.query< - SourceQuery.Query, - SourceQuery.Variables & { queryDeduplication: string } - >({ - query: sourceQuery, - fetchPolicy: 'cache-first', - variables: { - sourceId, - defaultIndex, - queryDeduplication, - }, - context: { - fetchOptions: { - signal: abortCtrl.signal, + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + text: msg.message, + title: i18n.FAIL_BEAT_FIELDS, + }); + } }, - }, - }); - - if (isSubscribed) { - setState({ - loading: false, - indicesExist: indicesExistOrDataTemporarilyUnavailable( - get('data.source.status.indicesExist', result) - ), - browserFields: getBrowserFields( - defaultIndex.join(), - get('data.source.status.indexFields', result) - ), - docValueFields: getDocValueFields( - defaultIndex.join(), - get('data.source.status.indexFields', result) - ), - indexPattern: getIndexFields( - defaultIndex.join(), - get('data.source.status.indexFields', result) - ), - errorMessage: null, }); - } - } catch (error) { - if (isSubscribed) { - setState((prevState) => ({ - ...prevState, - loading: false, - errorMessage: error.message, - })); - } - } + }; + abortCtrl.current.abort(); + asyncSearch(); + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts, onlyCheckIfIndicesExist] + ); + + useEffect(() => { + if (!isEmpty(indexNames) && !isEqual(previousIndexesName.current, indexNames)) { + indexFieldsSearch(indexNames); } + }, [indexNames, indexFieldsSearch, previousIndexesName]); + + return [isLoading, state]; +}; + +export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { + const { data, notifications } = useKibana().services; + const abortCtrl = useRef(new AbortController()); + const dispatch = useDispatch(); + const previousIndexesName = useRef([]); + + const indexNamesSelectedSelector = useMemo( + () => sourcererSelectors.getIndexNamesSelectedSelector(), + [] + ); + const indexNames = useSelector( + (state) => indexNamesSelectedSelector(state, sourcererScopeName), + shallowEqual + ); - fetchSource(); + const setLoading = useCallback( + (loading: boolean) => { + dispatch(sourcererActions.setSourcererScopeLoading({ id: sourcererScopeName, loading })); + }, + [dispatch, sourcererScopeName] + ); + + const indexFieldsSearch = useCallback( + (indicesName) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + const searchSubscription$ = data.search + .search( + { indices: indicesName, onlyCheckIfIndicesExist: false }, + { + abortSignal: abortCtrl.current.signal, + strategy: 'securitySolutionIndexFields', + } + ) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + const stringifyIndices = response.indicesExist.sort().join(); + previousIndexesName.current = response.indicesExist; + dispatch( + sourcererActions.setSource({ + id: sourcererScopeName, + payload: { + browserFields: getBrowserFields(stringifyIndices, response.indexFields), + docValueFields: getDocValueFields(stringifyIndices, response.indexFields), + errorMessage: null, + id: sourcererScopeName, + indexPattern: getIndexFields(stringifyIndices, response.indexFields), + indicesExist: response.indicesExist.length > 0, + loading: false, + }, + }) + ); + } + searchSubscription$.unsubscribe(); + } else if (!didCancel && response.isPartial && !response.isRunning) { + // TODO: Make response error status clearer + setLoading(false); + notifications.toasts.addWarning(i18n.ERROR_BEAT_FIELDS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!didCancel) { + setLoading(false); + } - return () => { - isSubscribed = false; - return abortCtrl.abort(); - }; - }, [apolloClient, sourceId, defaultIndex, queryDeduplication]); + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + text: msg.message, + title: i18n.FAIL_BEAT_FIELDS, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, dispatch, notifications.toasts, setLoading, sourcererScopeName] + ); - return state; + useEffect(() => { + if (!isEmpty(indexNames) && !isEqual(previousIndexesName.current, indexNames)) { + indexFieldsSearch(indexNames); + } + }, [indexNames, indexFieldsSearch, previousIndexesName]); }; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts index bba6a15d73970..7fcd11f71f081 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts +++ b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts @@ -5,347 +5,296 @@ */ import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; +import { DocValueFields } from '../../../../common/search_strategy'; +import { BrowserFields } from '../../../../common/search_strategy/index_fields'; -import { BrowserFields, DocValueFields } from '.'; -import { sourceQuery } from './index.gql_query'; - -export const mocksSource = [ - { - request: { - query: sourceQuery, - variables: { - sourceId: 'default', - defaultIndex: DEFAULT_INDEX_PATTERN, - }, +export const mocksSource = { + indexFields: [ + { + category: 'base', + description: + 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', + example: '2016-05-23T08:05:34.853Z', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: '@timestamp', + searchable: true, + type: 'date', + aggregatable: true, }, - result: { - data: { - source: { - id: 'default', - configuration: {}, - status: { - indicesExist: true, - winlogbeatIndices: [ - 'winlogbeat-7.0.0-2019.02.17', - 'winlogbeat-7.0.0-2019.02.18', - 'winlogbeat-7.0.0-2019.02.19', - 'winlogbeat-7.0.0-2019.02.20', - 'winlogbeat-7.0.0-2019.02.21', - 'winlogbeat-7.0.0-2019.02.21-000001', - 'winlogbeat-7.0.0-2019.02.22', - 'winlogbeat-8.0.0-2019.02.19-000001', - ], - auditbeatIndices: [ - 'auditbeat-7.0.0-2019.02.17', - 'auditbeat-7.0.0-2019.02.18', - 'auditbeat-7.0.0-2019.02.19', - 'auditbeat-7.0.0-2019.02.20', - 'auditbeat-7.0.0-2019.02.21', - 'auditbeat-7.0.0-2019.02.21-000001', - 'auditbeat-7.0.0-2019.02.22', - 'auditbeat-8.0.0-2019.02.19-000001', - ], - filebeatIndices: [ - 'filebeat-7.0.0-iot-2019.06', - 'filebeat-7.0.0-iot-2019.07', - 'filebeat-7.0.0-iot-2019.08', - 'filebeat-7.0.0-iot-2019.09', - 'filebeat-7.0.0-iot-2019.10', - 'filebeat-8.0.0-2019.02.19-000001', - ], - indexFields: [ - { - category: 'base', - description: - 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', - example: '2016-05-23T08:05:34.853Z', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: '@timestamp', - searchable: true, - type: 'date', - aggregatable: true, - }, - { - category: 'agent', - description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.ephemeral_id', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'agent', - description: null, - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.hostname', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'agent', - description: - 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', - example: '8a4f500d', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'agent', - description: - 'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', - example: 'foo', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.name', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a0', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a1', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a2', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'client', - description: - 'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.address', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'client', - description: 'Bytes sent from the client to the server.', - example: '184', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.bytes', - searchable: true, - type: 'number', - aggregatable: true, - }, - { - category: 'client', - description: 'Client domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.domain', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'client', - description: 'Country ISO code.', - example: 'CA', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.geo.country_iso_code', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'cloud', - description: - 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.', - example: '666777888999', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'cloud.account.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'cloud', - description: 'Availability zone in which this host is running.', - example: 'us-east-1c', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'cloud.availability_zone', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'container', - description: 'Unique container id.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'container', - description: 'Name of the image the container was built on.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.image.name', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'container', - description: 'Container image tag.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.image.tag', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'destination', - description: - 'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.address', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'destination', - description: 'Bytes sent from the destination to the source.', - example: '184', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.bytes', - searchable: true, - type: 'number', - aggregatable: true, - }, - { - category: 'destination', - description: 'Destination domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.domain', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - aggregatable: true, - category: 'destination', - description: - 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.ip', - searchable: true, - type: 'ip', - }, - { - aggregatable: true, - category: 'destination', - description: 'Port of the destination.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.port', - searchable: true, - type: 'long', - }, - { - aggregatable: true, - category: 'source', - description: - 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'source.ip', - searchable: true, - type: 'ip', - }, - { - aggregatable: true, - category: 'source', - description: 'Port of the source.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'source.port', - searchable: true, - type: 'long', - }, - { - aggregatable: true, - category: 'event', - description: - 'event.end contains the date when the event ended or when the activity was last observed.', - example: null, - format: '', - indexes: DEFAULT_INDEX_PATTERN, - name: 'event.end', - searchable: true, - type: 'date', - }, - ], - }, - }, - }, + { + category: 'agent', + description: + 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', + example: '8a4f500f', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'agent.ephemeral_id', + searchable: true, + type: 'string', + aggregatable: true, }, - }, -]; + { + category: 'agent', + description: null, + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'agent.hostname', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'agent', + description: + 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', + example: '8a4f500d', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'agent.id', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'agent', + description: + 'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', + example: 'foo', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'agent.name', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'auditd', + description: null, + example: null, + format: '', + indexes: ['auditbeat'], + name: 'auditd.data.a0', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'auditd', + description: null, + example: null, + format: '', + indexes: ['auditbeat'], + name: 'auditd.data.a1', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'auditd', + description: null, + example: null, + format: '', + indexes: ['auditbeat'], + name: 'auditd.data.a2', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'client', + description: + 'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'client.address', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'client', + description: 'Bytes sent from the client to the server.', + example: '184', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'client.bytes', + searchable: true, + type: 'number', + aggregatable: true, + }, + { + category: 'client', + description: 'Client domain.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'client.domain', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'client', + description: 'Country ISO code.', + example: 'CA', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'client.geo.country_iso_code', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'cloud', + description: + 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.', + example: '666777888999', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'cloud.account.id', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'cloud', + description: 'Availability zone in which this host is running.', + example: 'us-east-1c', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'cloud.availability_zone', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'container', + description: 'Unique container id.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'container.id', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'container', + description: 'Name of the image the container was built on.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'container.image.name', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'container', + description: 'Container image tag.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'container.image.tag', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'destination', + description: + 'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'destination.address', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'destination', + description: 'Bytes sent from the destination to the source.', + example: '184', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'destination.bytes', + searchable: true, + type: 'number', + aggregatable: true, + }, + { + category: 'destination', + description: 'Destination domain.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'destination.domain', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + aggregatable: true, + category: 'destination', + description: 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'destination.ip', + searchable: true, + type: 'ip', + }, + { + aggregatable: true, + category: 'destination', + description: 'Port of the destination.', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'destination.port', + searchable: true, + type: 'long', + }, + { + aggregatable: true, + category: 'source', + description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'source.ip', + searchable: true, + type: 'ip', + }, + { + aggregatable: true, + category: 'source', + description: 'Port of the source.', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'source.port', + searchable: true, + type: 'long', + }, + { + aggregatable: true, + category: 'event', + description: + 'event.end contains the date when the event ended or when the activity was last observed.', + example: null, + format: '', + indexes: DEFAULT_INDEX_PATTERN, + name: 'event.end', + searchable: true, + type: 'date', + }, + ], +}; export const mockIndexFields = [ { aggregatable: true, name: '@timestamp', searchable: true, type: 'date' }, diff --git a/x-pack/plugins/security_solution/public/common/containers/source/translations.ts b/x-pack/plugins/security_solution/public/common/containers/source/translations.ts new file mode 100644 index 0000000000000..f12a9a0b41a7b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/source/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_BEAT_FIELDS = i18n.translate( + 'xpack.securitySolution.beatFields.errorSearchDescription', + { + defaultMessage: `An error has occurred on getting beat fields`, + } +); + +export const FAIL_BEAT_FIELDS = i18n.translate( + 'xpack.securitySolution.beatFields.failSearchDescription', + { + defaultMessage: `Failed to run search on beat fields`, + } +); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts index 106294ba54f5a..be3d074811032 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts @@ -4,26 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const SOURCERER_FEATURE_FLAG_ON = false; - -export enum SecurityPageName { - default = 'default', - host = 'host', - detections = 'detections', - timeline = 'timeline', - network = 'network', -} - -export type SourceGroupsType = keyof typeof SecurityPageName; - -export const sourceGroups = { - [SecurityPageName.default]: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'winlogbeat-*', - 'blobbeat-*', - ], -}; +export const SOURCERER_FEATURE_FLAG_ON = true; diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.test.tsx deleted file mode 100644 index b8017df09b738..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.test.tsx +++ /dev/null @@ -1,23 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { indicesExistOrDataTemporarilyUnavailable } from './format'; - -describe('indicesExistOrDataTemporarilyUnavailable', () => { - it('it returns true when undefined', () => { - let undefVar; - const result = indicesExistOrDataTemporarilyUnavailable(undefVar); - expect(result).toBeTruthy(); - }); - it('it returns true when true', () => { - const result = indicesExistOrDataTemporarilyUnavailable(true); - expect(result).toBeTruthy(); - }); - it('it returns false when false', () => { - const result = indicesExistOrDataTemporarilyUnavailable(false); - expect(result).toBeFalsy(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.ts deleted file mode 100644 index 8c9a16ed705ef..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { isEmpty, pick } from 'lodash/fp'; -import memoizeOne from 'memoize-one'; -import { set } from '@elastic/safer-lodash-set/fp'; -import { isUndefined } from 'lodash'; -import { IndexField } from '../../../graphql/types'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; - -export interface BrowserField { - aggregatable: boolean; - category: string; - description: string | null; - example: string | number | null; - fields: Readonly>>; - format: string; - indexes: string[]; - name: string; - searchable: boolean; - type: string; -} - -export interface DocValueFields { - field: string; - format: string; -} - -export type BrowserFields = Readonly>>; - -export const getAllBrowserFields = (browserFields: BrowserFields): Array> => - Object.values(browserFields).reduce>>( - (acc, namespace) => [ - ...acc, - ...Object.values(namespace.fields != null ? namespace.fields : {}), - ], - [] - ); - -export const getIndexFields = memoizeOne( - (title: string, fields: IndexField[]): IIndexPattern => - fields && fields.length > 0 - ? { - fields: fields.map((field) => - pick(['name', 'searchable', 'type', 'aggregatable', 'esTypes', 'subType'], field) - ), - title, - } - : { fields: [], title }, - (newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1].length === lastArgs[1].length -); - -export const getBrowserFields = memoizeOne( - (_title: string, fields: IndexField[]): BrowserFields => - fields && fields.length > 0 - ? fields.reduce( - (accumulator: BrowserFields, field: IndexField) => - set([field.category, 'fields', field.name], field, accumulator), - {} - ) - : {}, - // Update the value only if _title has changed - (newArgs, lastArgs) => newArgs[0] === lastArgs[0] -); - -export const getDocValueFields = memoizeOne( - (_title: string, fields: IndexField[]): DocValueFields[] => - fields && fields.length > 0 - ? fields.reduce((accumulator: DocValueFields[], field: IndexField) => { - if (field.type === 'date' && accumulator.length < 100) { - const format: string = - field.format != null && !isEmpty(field.format) ? field.format : 'date_time'; - return [ - ...accumulator, - { - field: field.name, - format, - }, - ]; - } - return accumulator; - }, []) - : [], - // Update the value only if _title has changed - (newArgs, lastArgs) => newArgs[0] === lastArgs[0] -); - -export const indicesExistOrDataTemporarilyUnavailable = ( - indicesExist: boolean | null | undefined -) => indicesExist || isUndefined(indicesExist); - -export const EMPTY_BROWSER_FIELDS = {}; -export const EMPTY_DOCVALUE_FIELD: DocValueFields[] = []; diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx index 38af84e0968f8..673db7af2b5e6 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx @@ -4,28 +4,73 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + +import React from 'react'; import { act, renderHook } from '@testing-library/react-hooks'; +import { Provider } from 'react-redux'; -import { getSourceDefaults, useSourceManager, UseSourceManager } from '.'; +import { useInitSourcerer } from '.'; +import { mockPatterns, mockSource } from './mocks'; +// import { SourcererScopeName } from '../../store/sourcerer/model'; +import { RouteSpyState } from '../../utils/route/types'; +import { SecurityPageName } from '../../../../common/constants'; +import { createStore, State } from '../../store'; import { - mockSourceSelections, - mockSourceGroup, - mockSourceGroups, - mockPatterns, - mockSource, -} from './mocks'; -import { SecurityPageName } from './constants'; -const mockSourceDefaults = mockSource(SecurityPageName.default); + apolloClientObservable, + createSecuritySolutionStorageMock, + kibanaObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, +} from '../../mock'; +const mockSourceDefaults = mockSource; + +const mockRouteSpy: RouteSpyState = { + pageName: SecurityPageName.overview, + detailName: undefined, + tabName: undefined, + search: '', + pathName: '/', +}; +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + + return { + ...original, + useDispatch: () => mockDispatch, + }; +}); +jest.mock('../../utils/route/use_route_spy', () => ({ + useRouteSpy: () => [mockRouteSpy], +})); jest.mock('../../lib/kibana', () => ({ useKibana: jest.fn().mockReturnValue({ services: { + application: { + capabilities: { + siem: { + crud: true, + }, + }, + }, data: { indexPatterns: { getTitles: jest.fn().mockImplementation(() => Promise.resolve(mockPatterns)), }, + search: { + search: jest.fn().mockImplementation(() => ({ + subscribe: jest.fn().mockImplementation(() => ({ + error: jest.fn(), + next: jest.fn(), + })), + })), + }, }, + notifications: {}, }, }), + useUiSetting$: jest.fn().mockImplementation(() => [mockPatterns]), })); jest.mock('../../utils/apollo_context', () => ({ useApolloClient: jest.fn().mockReturnValue({ @@ -34,148 +79,193 @@ jest.mock('../../utils/apollo_context', () => ({ })); describe('Sourcerer Hooks', () => { - const testId = SecurityPageName.default; - const uninitializedId = SecurityPageName.host; + // const testId = SourcererScopeName.default; + // const uninitializedId = SourcererScopeName.detections; beforeEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); }); + const state: State = mockGlobalState; + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore( + state, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage + ); + + beforeEach(() => { + store = createStore( + state, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage + ); + }); describe('Initialization', () => { - it('initializes loading default index patterns', async () => { + it('initializes loading default and timeline index patterns', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - expect(result.current).toEqual({ - activeSourceGroupId: 'default', - availableIndexPatterns: [], - availableSourceGroupIds: [], - isIndexPatternsLoading: true, - sourceGroups: {}, - getManageSourceGroupById: result.current.getManageSourceGroupById, - initializeSourceGroup: result.current.initializeSourceGroup, - setActiveSourceGroupId: result.current.setActiveSourceGroupId, - updateSourceGroupIndicies: result.current.updateSourceGroupIndicies, + const { waitForNextUpdate } = renderHook(() => useInitSourcerer(), { + wrapper: ({ children }) => {children}, }); - }); - }); - it('initializes loading default source group', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); await waitForNextUpdate(); await waitForNextUpdate(); - expect(result.current).toEqual({ - activeSourceGroupId: 'default', - availableIndexPatterns: mockPatterns, - availableSourceGroupIds: [], - isIndexPatternsLoading: false, - sourceGroups: {}, - getManageSourceGroupById: result.current.getManageSourceGroupById, - initializeSourceGroup: result.current.initializeSourceGroup, - setActiveSourceGroupId: result.current.setActiveSourceGroupId, - updateSourceGroupIndicies: result.current.updateSourceGroupIndicies, + expect(mockDispatch).toBeCalledTimes(2); + expect(mockDispatch.mock.calls[0][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + payload: { id: 'default', loading: true }, }); - }); - }); - it('initialize completes with formatted source group data', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current).toEqual({ - activeSourceGroupId: testId, - availableIndexPatterns: mockPatterns, - availableSourceGroupIds: [testId], - isIndexPatternsLoading: false, - sourceGroups: { - default: mockSourceGroup(testId), - }, - getManageSourceGroupById: result.current.getManageSourceGroupById, - initializeSourceGroup: result.current.initializeSourceGroup, - setActiveSourceGroupId: result.current.setActiveSourceGroupId, - updateSourceGroupIndicies: result.current.updateSourceGroupIndicies, + expect(mockDispatch.mock.calls[1][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + payload: { id: 'timeline', loading: true }, }); + // expect(mockDispatch.mock.calls[1][0]).toEqual({ + // type: 'x-pack/security_solution/local/sourcerer/SET_INDEX_PATTERNS_LIST', + // payload: { allIndexPatterns: mockPatterns, kibanaIndexPatterns: [] }, + // }); }); }); + // TO DO sourcerer @S + // it('initializes loading default source group', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // expect(result.current).toEqual({ + // activeSourcererScopeId: 'default', + // kibanaIndexPatterns: mockPatterns, + // isIndexPatternsLoading: false, + // getSourcererScopeById: result.current.getSourcererScopeById, + // setActiveSourcererScopeId: result.current.setActiveSourcererScopeId, + // updateSourcererScopeIndices: result.current.updateSourcererScopeIndices, + // }); + // }); + // }); + // it('initialize completes with formatted source group data', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // expect(result.current).toEqual({ + // activeSourcererScopeId: testId, + // kibanaIndexPatterns: mockPatterns, + // isIndexPatternsLoading: false, + // getSourcererScopeById: result.current.getSourcererScopeById, + // setActiveSourcererScopeId: result.current.setActiveSourcererScopeId, + // updateSourcererScopeIndices: result.current.updateSourcererScopeIndices, + // }); + // }); + // }); }); - describe('Methods', () => { - it('getManageSourceGroupById: initialized source group returns defaults', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - const initializedSourceGroup = result.current.getManageSourceGroupById(testId); - expect(initializedSourceGroup).toEqual(mockSourceGroup(testId)); - }); - }); - it('getManageSourceGroupById: uninitialized source group returns defaults', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - const uninitializedSourceGroup = result.current.getManageSourceGroupById(uninitializedId); - expect(uninitializedSourceGroup).toEqual(getSourceDefaults(uninitializedId, mockPatterns)); - }); - }); - it('initializeSourceGroup: initializes source group', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - result.current.initializeSourceGroup( - uninitializedId, - mockSourceGroups[uninitializedId], - true - ); - await waitForNextUpdate(); - const initializedSourceGroup = result.current.getManageSourceGroupById(uninitializedId); - expect(initializedSourceGroup.indexPatterns).toEqual(mockSourceSelections[uninitializedId]); - }); - }); - it('setActiveSourceGroupId: active source group id gets set only if it gets initialized first', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - expect(result.current.activeSourceGroupId).toEqual(testId); - result.current.setActiveSourceGroupId(uninitializedId); - expect(result.current.activeSourceGroupId).toEqual(testId); - result.current.initializeSourceGroup(uninitializedId); - result.current.setActiveSourceGroupId(uninitializedId); - expect(result.current.activeSourceGroupId).toEqual(uninitializedId); - }); - }); - it('updateSourceGroupIndicies: updates source group indicies', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - let sourceGroup = result.current.getManageSourceGroupById(testId); - expect(sourceGroup.indexPatterns).toEqual(mockSourceSelections[testId]); - result.current.updateSourceGroupIndicies(testId, ['endgame-*', 'filebeat-*']); - await waitForNextUpdate(); - sourceGroup = result.current.getManageSourceGroupById(testId); - expect(sourceGroup.indexPatterns).toEqual(['endgame-*', 'filebeat-*']); - }); - }); - }); + // describe('Methods', () => { + // it('getSourcererScopeById: initialized source group returns defaults', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // const initializedSourcererScope = result.current.getSourcererScopeById(testId); + // expect(initializedSourcererScope).toEqual(mockSourcererScope(testId)); + // }); + // }); + // it('getSourcererScopeById: uninitialized source group returns defaults', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // const uninitializedSourcererScope = result.current.getSourcererScopeById(uninitializedId); + // expect(uninitializedSourcererScope).toEqual( + // getSourceDefaults(uninitializedId, mockPatterns) + // ); + // }); + // }); + // // it('initializeSourcererScope: initializes source group', async () => { + // // await act(async () => { + // // const { result, waitForNextUpdate } = renderHook( + // // () => useSourcerer(), + // // { + // // wrapper: ({ children }) => {children}, + // // } + // // ); + // // await waitForNextUpdate(); + // // await waitForNextUpdate(); + // // await waitForNextUpdate(); + // // result.current.initializeSourcererScope( + // // uninitializedId, + // // mockSourcererScopes[uninitializedId], + // // true + // // ); + // // await waitForNextUpdate(); + // // const initializedSourcererScope = result.current.getSourcererScopeById(uninitializedId); + // // expect(initializedSourcererScope.selectedPatterns).toEqual( + // // mockSourcererScopes[uninitializedId] + // // ); + // // }); + // // }); + // it('setActiveSourcererScopeId: active source group id gets set only if it gets initialized first', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // expect(result.current.activeSourcererScopeId).toEqual(testId); + // result.current.setActiveSourcererScopeId(uninitializedId); + // expect(result.current.activeSourcererScopeId).toEqual(testId); + // // result.current.initializeSourcererScope(uninitializedId); + // result.current.setActiveSourcererScopeId(uninitializedId); + // expect(result.current.activeSourcererScopeId).toEqual(uninitializedId); + // }); + // }); + // it('updateSourcererScopeIndices: updates source group indices', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // let sourceGroup = result.current.getSourcererScopeById(testId); + // expect(sourceGroup.selectedPatterns).toEqual(mockSourcererScopes[testId]); + // expect(sourceGroup.scopePatterns).toEqual(mockSourcererScopes[testId]); + // result.current.updateSourcererScopeIndices({ + // id: testId, + // selectedPatterns: ['endgame-*', 'filebeat-*'], + // }); + // await waitForNextUpdate(); + // sourceGroup = result.current.getSourcererScopeById(testId); + // expect(sourceGroup.scopePatterns).toEqual(mockSourcererScopes[testId]); + // expect(sourceGroup.selectedPatterns).toEqual(['endgame-*', 'filebeat-*']); + // }); + // }); + // }); }); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx index 91907b45aa449..b02a09625ccf3 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx @@ -4,412 +4,74 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get, noop, isEmpty } from 'lodash/fp'; -import React, { createContext, useCallback, useContext, useEffect, useReducer } from 'react'; -import { IIndexPattern } from 'src/plugins/data/public'; - -import { NO_ALERT_INDEX } from '../../../../common/constants'; -import { useKibana } from '../../lib/kibana'; - -import { SourceQuery } from '../../../graphql/types'; - -import { sourceQuery } from '../source/index.gql_query'; -import { useApolloClient } from '../../utils/apollo_context'; -import { - sourceGroups, - SecurityPageName, - SourceGroupsType, - SOURCERER_FEATURE_FLAG_ON, -} from './constants'; -import { - BrowserFields, - DocValueFields, - EMPTY_BROWSER_FIELDS, - EMPTY_DOCVALUE_FIELD, - getBrowserFields, - getDocValueFields, - getIndexFields, - indicesExistOrDataTemporarilyUnavailable, -} from './format'; - -// TYPES -interface ManageSource { - browserFields: BrowserFields; - defaultPatterns: string[]; - docValueFields: DocValueFields[]; - errorMessage: string | null; - id: SourceGroupsType; - indexPattern: IIndexPattern; - indexPatterns: string[]; - indicesExist: boolean | undefined | null; - loading: boolean; -} - -interface ManageSourceInit extends Partial { - id: SourceGroupsType; -} - -type ManageSourceGroupById = { - [id in SourceGroupsType]?: ManageSource; -}; - -type ActionManageSource = - | { - type: 'SET_SOURCE'; - id: SourceGroupsType; - defaultIndex: string[]; - payload: ManageSourceInit; - } - | { - type: 'SET_IS_SOURCE_LOADING'; - id: SourceGroupsType; - payload: boolean; - } - | { - type: 'SET_ACTIVE_SOURCE_GROUP_ID'; - payload: SourceGroupsType; - } - | { - type: 'SET_AVAILABLE_INDEX_PATTERNS'; - payload: string[]; - } - | { - type: 'SET_IS_INDEX_PATTERNS_LOADING'; - payload: boolean; - }; - -interface ManageSourcerer { - activeSourceGroupId: SourceGroupsType; - availableIndexPatterns: string[]; - availableSourceGroupIds: SourceGroupsType[]; - isIndexPatternsLoading: boolean; - sourceGroups: ManageSourceGroupById; -} - -export interface UseSourceManager extends ManageSourcerer { - getManageSourceGroupById: (id: SourceGroupsType) => ManageSource; - initializeSourceGroup: ( - id: SourceGroupsType, - indexToAdd?: string[] | null, - onlyCheckIndexToAdd?: boolean - ) => void; - setActiveSourceGroupId: (id: SourceGroupsType) => void; - updateSourceGroupIndicies: (id: SourceGroupsType, updatedIndicies: string[]) => void; -} - -// DEFAULTS/INIT -export const getSourceDefaults = (id: SourceGroupsType, defaultIndex: string[]) => ({ - browserFields: EMPTY_BROWSER_FIELDS, - defaultPatterns: defaultIndex, - docValueFields: EMPTY_DOCVALUE_FIELD, - errorMessage: null, - id, - indexPattern: getIndexFields(defaultIndex.join(), []), - indexPatterns: defaultIndex, - indicesExist: indicesExistOrDataTemporarilyUnavailable(undefined), - loading: true, -}); - -const initManageSource: ManageSourcerer = { - activeSourceGroupId: SecurityPageName.default, - availableIndexPatterns: [], - availableSourceGroupIds: [], - isIndexPatternsLoading: true, - sourceGroups: {}, -}; -const init: UseSourceManager = { - ...initManageSource, - getManageSourceGroupById: (id: SourceGroupsType) => getSourceDefaults(id, []), - initializeSourceGroup: () => noop, - setActiveSourceGroupId: () => noop, - updateSourceGroupIndicies: () => noop, -}; - -const reducerManageSource = (state: ManageSourcerer, action: ActionManageSource) => { - switch (action.type) { - case 'SET_SOURCE': - return { - ...state, - sourceGroups: { - ...state.sourceGroups, - [action.id]: { - ...getSourceDefaults(action.id, action.defaultIndex), - ...state.sourceGroups[action.id], - ...action.payload, - }, - }, - availableSourceGroupIds: state.availableSourceGroupIds.includes(action.id) - ? state.availableSourceGroupIds - : [...state.availableSourceGroupIds, action.id], - }; - case 'SET_IS_SOURCE_LOADING': - return { - ...state, - sourceGroups: { - ...state.sourceGroups, - [action.id]: { - ...state.sourceGroups[action.id], - id: action.id, - loading: action.payload, - }, - }, - }; - case 'SET_ACTIVE_SOURCE_GROUP_ID': - return { - ...state, - activeSourceGroupId: action.payload, - }; - case 'SET_AVAILABLE_INDEX_PATTERNS': - return { - ...state, - availableIndexPatterns: action.payload, - }; - case 'SET_IS_INDEX_PATTERNS_LOADING': - return { - ...state, - isIndexPatternsLoading: action.payload, - }; - default: - return state; - } -}; - -// HOOKS -export const useSourceManager = (): UseSourceManager => { - const { - services: { - data: { indexPatterns }, - }, - } = useKibana(); - const apolloClient = useApolloClient(); - const [state, dispatch] = useReducer(reducerManageSource, initManageSource); - - // Kibana Index Patterns - const setIsIndexPatternsLoading = useCallback((loading: boolean) => { - dispatch({ - type: 'SET_IS_INDEX_PATTERNS_LOADING', - payload: loading, - }); - }, []); - const getDefaultIndex = useCallback( - (indexToAdd?: string[] | null, onlyCheckIndexToAdd?: boolean) => { - const filterIndexAdd = (indexToAdd ?? []).filter((item) => item !== NO_ALERT_INDEX); - if (!isEmpty(filterIndexAdd)) { - return onlyCheckIndexToAdd - ? filterIndexAdd.sort() - : [ - ...state.availableIndexPatterns, - ...filterIndexAdd.filter((index) => !state.availableIndexPatterns.includes(index)), - ].sort(); - } - return state.availableIndexPatterns.sort(); - }, - [state.availableIndexPatterns] - ); - const setAvailableIndexPatterns = useCallback((availableIndexPatterns: string[]) => { - dispatch({ - type: 'SET_AVAILABLE_INDEX_PATTERNS', - payload: availableIndexPatterns, - }); - }, []); - const fetchKibanaIndexPatterns = useCallback(() => { - setIsIndexPatternsLoading(true); - const abortCtrl = new AbortController(); - - async function fetchTitles() { - try { - const result = await indexPatterns.getTitles(); - setAvailableIndexPatterns(result); - setIsIndexPatternsLoading(false); - } catch (error) { - setIsIndexPatternsLoading(false); - } - } - - fetchTitles(); - - return () => { - return abortCtrl.abort(); - }; - }, [indexPatterns, setAvailableIndexPatterns, setIsIndexPatternsLoading]); - - // Security Solution Source Groups - const setActiveSourceGroupId = useCallback( - (sourceGroupId: SourceGroupsType) => { - if (state.availableSourceGroupIds.includes(sourceGroupId)) { - dispatch({ - type: 'SET_ACTIVE_SOURCE_GROUP_ID', - payload: sourceGroupId, - }); - } - }, - [state.availableSourceGroupIds] - ); - const setIsSourceLoading = useCallback( - ({ id, loading }: { id: SourceGroupsType; loading: boolean }) => { - dispatch({ - type: 'SET_IS_SOURCE_LOADING', - id, - payload: loading, - }); - }, +import deepEqual from 'fast-deep-equal'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports +import isEqual from 'lodash/isEqual'; +import { useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; +import { ManageScope, SourcererScopeName } from '../../store/sourcerer/model'; +import { useIndexFields } from '../source'; +import { State } from '../../store'; +import { useUserInfo } from '../../../detections/components/user_info'; + +export const useInitSourcerer = ( + scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default +) => { + const dispatch = useDispatch(); + + const { loading: loadingSignalIndex, isSignalIndexExists, signalIndexName } = useUserInfo(); + const getConfigIndexPatternsSelector = useMemo( + () => sourcererSelectors.configIndexPatternsSelector(), [] ); - const enrichSource = useCallback( - (id: SourceGroupsType, indexToAdd?: string[] | null, onlyCheckIndexToAdd?: boolean) => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - const defaultIndex = getDefaultIndex(indexToAdd, onlyCheckIndexToAdd); - const selectedPatterns = defaultIndex.filter((pattern) => - state.availableIndexPatterns.includes(pattern) - ); - if (state.sourceGroups[id] == null) { - dispatch({ - type: 'SET_SOURCE', - id, - defaultIndex: selectedPatterns, - payload: { defaultPatterns: defaultIndex, id }, - }); - } - - async function fetchSource() { - if (!apolloClient) return; - setIsSourceLoading({ id, loading: true }); - try { - const result = await apolloClient.query({ - query: sourceQuery, - fetchPolicy: 'network-only', - variables: { - sourceId: 'default', // always - defaultIndex: selectedPatterns, - }, - context: { - fetchOptions: { - signal: abortCtrl.signal, - }, - }, - }); - if (isSubscribed) { - dispatch({ - type: 'SET_SOURCE', - id, - defaultIndex: selectedPatterns, - payload: { - browserFields: getBrowserFields( - selectedPatterns.join(), - get('data.source.status.indexFields', result) - ), - docValueFields: getDocValueFields( - selectedPatterns.join(), - get('data.source.status.indexFields', result) - ), - errorMessage: null, - id, - indexPattern: getIndexFields( - selectedPatterns.join(), - get('data.source.status.indexFields', result) - ), - indexPatterns: selectedPatterns, - indicesExist: indicesExistOrDataTemporarilyUnavailable( - get('data.source.status.indicesExist', result) - ), - loading: false, - }, - }); - } - } catch (error) { - if (isSubscribed) { - dispatch({ - type: 'SET_SOURCE', - id, - defaultIndex: selectedPatterns, - payload: { - errorMessage: error.message, - id, - loading: false, - }, - }); - } - } - } - - fetchSource(); - - return () => { - isSubscribed = false; - return abortCtrl.abort(); - }; - }, - [ - apolloClient, - getDefaultIndex, - setIsSourceLoading, - state.availableIndexPatterns, - state.sourceGroups, - ] - ); + const ConfigIndexPatterns = useSelector(getConfigIndexPatternsSelector, isEqual); - const initializeSourceGroup = useCallback( - (id: SourceGroupsType, indexToAdd?: string[] | null, onlyCheckIndexToAdd?: boolean) => - enrichSource(id, indexToAdd, onlyCheckIndexToAdd), - [enrichSource] - ); - - const updateSourceGroupIndicies = useCallback( - (id: SourceGroupsType, updatedIndicies: string[]) => enrichSource(id, updatedIndicies, true), - [enrichSource] - ); - const getManageSourceGroupById = useCallback( - (id: SourceGroupsType) => { - const sourceById = state.sourceGroups[id]; - if (sourceById != null) { - return sourceById; - } - return getSourceDefaults(id, getDefaultIndex()); - }, - [getDefaultIndex, state.sourceGroups] - ); + useIndexFields(scopeId); + useIndexFields(SourcererScopeName.timeline); - // load initial default index useEffect(() => { - fetchKibanaIndexPatterns(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + if (!loadingSignalIndex && signalIndexName != null) { + dispatch(sourcererActions.setSignalIndexName({ signalIndexName })); + } + }, [dispatch, loadingSignalIndex, signalIndexName]); + // Related to timeline useEffect(() => { - if (!state.isIndexPatternsLoading) { - Object.entries(sourceGroups).forEach(([key, value]) => - initializeSourceGroup(key as SourceGroupsType, value, true) + if (!loadingSignalIndex && signalIndexName != null) { + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: SourcererScopeName.timeline, + selectedPatterns: [...ConfigIndexPatterns, signalIndexName], + }) ); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state.isIndexPatternsLoading]); + }, [ConfigIndexPatterns, dispatch, loadingSignalIndex, signalIndexName]); - return { - ...state, - getManageSourceGroupById, - initializeSourceGroup, - setActiveSourceGroupId, - updateSourceGroupIndicies, - }; + // Related to the detection page + useEffect(() => { + if ( + scopeId === SourcererScopeName.detections && + isSignalIndexExists && + signalIndexName != null + ) { + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: scopeId, + selectedPatterns: [signalIndexName], + }) + ); + } + }, [dispatch, isSignalIndexExists, scopeId, signalIndexName]); }; -const ManageSourceContext = createContext(init); - -export const useManageSource = () => useContext(ManageSourceContext); - -interface ManageSourceProps { - children: React.ReactNode; -} - -export const MaybeManageSource = ({ children }: ManageSourceProps) => { - const indexPatternManager = useSourceManager(); - return ( - - {children} - +export const useSourcererScope = (scope: SourcererScopeName = SourcererScopeName.default) => { + const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); + const SourcererScope = useSelector( + (state) => sourcererScopeSelector(state, scope), + deepEqual ); + return SourcererScope; }; -export const ManageSource = SOURCERER_FEATURE_FLAG_ON - ? MaybeManageSource - : ({ children }: ManageSourceProps) => <>{children}; diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts index cde14e54694f0..c34a6917f300e 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SecurityPageName } from './constants'; -import { getSourceDefaults } from './index'; +import { initSourcererScope } from '../../store/sourcerer/model'; export const mockPatterns = [ 'auditbeat-*', @@ -14,32 +13,10 @@ export const mockPatterns = [ 'logs-*', 'packetbeat-*', 'winlogbeat-*', + 'journalbeat-*', ]; -export const mockSourceGroups = { - [SecurityPageName.default]: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'blobbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'winlogbeat-*', - ], - [SecurityPageName.host]: [ - 'apm-*-transaction*', - 'endgame-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], -}; - -export const mockSourceSelections = { - [SecurityPageName.default]: ['auditbeat-*', 'endgame-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], - [SecurityPageName.host]: ['endgame-*', 'logs-*', 'packetbeat-*', 'winlogbeat-*'], -}; -export const mockSource = (testId: SecurityPageName.default | SecurityPageName.host) => ({ +export const mockSource = { data: { source: { id: 'default', @@ -50,7 +27,7 @@ export const mockSource = (testId: SecurityPageName.default | SecurityPageName.h category: '_id', description: 'Each document has an _id that uniquely identifies it', example: 'Y-6TfmcB0WOhS6qyMv3s', - indexes: mockSourceSelections[testId], + indexes: mockPatterns, name: '_id', searchable: true, type: 'string', @@ -67,48 +44,45 @@ export const mockSource = (testId: SecurityPageName.default | SecurityPageName.h loading: false, networkStatus: 7, stale: false, -}); +}; -export const mockSourceGroup = (testId: SecurityPageName.default | SecurityPageName.host) => { - const indexes = mockSourceSelections[testId]; - return { - ...getSourceDefaults(testId, mockPatterns), - defaultPatterns: mockSourceGroups[testId], - browserFields: { - _id: { - fields: { - _id: { - __typename: 'IndexField', - aggregatable: false, - category: '_id', - description: 'Each document has an _id that uniquely identifies it', - esTypes: null, - example: 'Y-6TfmcB0WOhS6qyMv3s', - format: null, - indexes, - name: '_id', - searchable: true, - subType: null, - type: 'string', - }, - }, - }, - }, - indexPattern: { - fields: [ - { +export const mockSourcererScope = { + ...initSourcererScope, + scopePatterns: mockPatterns, + browserFields: { + _id: { + fields: { + _id: { + __typename: 'IndexField', aggregatable: false, + category: '_id', + description: 'Each document has an _id that uniquely identifies it', esTypes: null, + example: 'Y-6TfmcB0WOhS6qyMv3s', + format: null, + indexes: mockPatterns, name: '_id', searchable: true, subType: null, type: 'string', }, - ], - title: indexes.join(), + }, }, - indexPatterns: indexes, - indicesExist: true, - loading: false, - }; + }, + indexPattern: { + fields: [ + { + aggregatable: false, + esTypes: null, + name: '_id', + searchable: true, + subType: null, + type: 'string', + }, + ], + title: mockPatterns.join(), + }, + selectedPatterns: mockPatterns, + indicesExist: true, + loading: false, }; diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts index 573ef92f7e069..3051459d5de0c 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts @@ -12,9 +12,25 @@ import { createStartServicesMock, createWithKibanaMock, } from '../kibana_react.mock'; - +const mockStartServicesMock = createStartServicesMock(); export const KibanaServices = { get: jest.fn(), getKibanaVersion: jest.fn(() => '8.0.0') }; -export const useKibana = jest.fn().mockReturnValue({ services: createStartServicesMock() }); +export const useKibana = jest.fn().mockReturnValue({ + services: { + ...mockStartServicesMock, + data: { + ...mockStartServicesMock.data, + search: { + ...mockStartServicesMock.data.search, + search: jest.fn().mockImplementation(() => ({ + subscribe: jest.fn().mockImplementation(() => ({ + error: jest.fn(), + next: jest.fn(), + })), + })), + }, + }, + }, +}); export const useUiSetting = jest.fn(createUseUiSettingMock()); export const useUiSetting$ = jest.fn(createUseUiSetting$Mock()); export const useHttp = jest.fn().mockReturnValue(createStartServicesMock().http); diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx index 73c0f00573911..3b7262e8a8d7e 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx @@ -11,7 +11,7 @@ import { Router } from 'react-router-dom'; import { History } from 'history'; import { useObservable } from 'react-use'; import { Store } from 'redux'; -import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components'; +import { EuiThemeProvider } from '../../../../../xpack_legacy/common'; import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; import { RouteCapture } from '../../components/endpoint/route_capture'; import { StartPlugins } from '../../../types'; diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index a74c9a6d2009d..0944b6aa27f67 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -22,11 +22,15 @@ import { DEFAULT_TO, DEFAULT_INTERVAL_TYPE, DEFAULT_INTERVAL_VALUE, + DEFAULT_INDEX_PATTERN, } from '../../../common/constants'; import { networkModel } from '../../network/store'; import { TimelineType, TimelineStatus } from '../../../common/types/timeline'; import { mockManagementState } from '../../management/store/reducer'; import { ManagementState } from '../../management/types'; +import { initialSourcererState, SourcererScopeName } from '../store/sourcerer/model'; +import { mockBrowserFields, mockDocValueFields } from '../containers/source/mock'; +import { mockIndexPattern } from './index_pattern'; export const mockGlobalState: State = { app: { @@ -203,6 +207,7 @@ export const mockGlobalState: State = { id: 'test', savedObjectId: null, columns: defaultHeaders, + indexNames: DEFAULT_INDEX_PATTERN, itemsPerPage: 5, dataProviders: [], description: '', @@ -241,6 +246,28 @@ export const mockGlobalState: State = { }, insertTimeline: null, }, + sourcerer: { + ...initialSourcererState, + sourcererScopes: { + ...initialSourcererState.sourcererScopes, + [SourcererScopeName.default]: { + ...initialSourcererState.sourcererScopes[SourcererScopeName.default], + selectedPatterns: DEFAULT_INDEX_PATTERN, + browserFields: mockBrowserFields, + indexPattern: mockIndexPattern, + docValueFields: mockDocValueFields, + loading: false, + }, + [SourcererScopeName.timeline]: { + ...initialSourcererState.sourcererScopes[SourcererScopeName.timeline], + selectedPatterns: DEFAULT_INDEX_PATTERN, + browserFields: mockBrowserFields, + indexPattern: mockIndexPattern, + docValueFields: mockDocValueFields, + loading: false, + }, + }, + }, /** * These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture, * they are cast to mutable versions here. diff --git a/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts b/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts index 826057560f942..e4abc17e9034c 100644 --- a/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts +++ b/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export const mockIndexPattern = { +import { IIndexPattern } from '../../../../../../src/plugins/data/common/index_patterns'; + +export const mockIndexPattern: IIndexPattern = { fields: [ { name: '@timestamp', @@ -93,3 +95,5 @@ export const mockIndexPattern = { ], title: 'filebeat-*,auditbeat-*,packetbeat-*', }; + +export const mockIndexNames = ['filebeat-*', 'auditbeat-*', 'packetbeat-*']; diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts index 2395010a0ba2e..c5d881c540eec 100644 --- a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts +++ b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DetailItem } from '../../graphql/types'; +import { TimelineEventsDetailsItem } from '../../../common/search_strategy'; export const mockDetailItemDataId = 'Y-6TfmcB0WOhS6qyMv3s'; -export const mockDetailItemData: DetailItem[] = [ +export const mockDetailItemData: TimelineEventsDetailsItem[] = [ { field: '_id', originalValue: 'pEMaMmkBUV60JmNWmWVi', diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index 26013915315af..9f26fc22ede53 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -8,13 +8,8 @@ import { FilterStateStore } from '../../../../../../src/plugins/data/common/es_q import { TimelineId, TimelineType, TimelineStatus } from '../../../common/types/timeline'; import { OpenTimelineResult } from '../../timelines/components/open_timeline/types'; -import { - GetAllTimeline, - SortFieldTimeline, - TimelineResult, - Direction, - DetailItem, -} from '../../graphql/types'; +import { GetAllTimeline, SortFieldTimeline, TimelineResult, Direction } from '../../graphql/types'; +import { TimelineEventsDetailsItem } from '../../../common/search_strategy'; import { allTimelinesQuery } from '../../timelines/containers/all/index.gql_query'; import { CreateTimelineProps } from '../../detections/components/alerts_table/types'; import { TimelineModel } from '../../timelines/store/timeline/model'; @@ -2124,6 +2119,7 @@ export const mockTimelineModel: TimelineModel = { highlightedDropAndProviderId: '', historyIds: [], id: 'ef579e40-jibber-jabber', + indexNames: [], isFavorite: false, isLive: false, isLoading: false, @@ -2228,6 +2224,7 @@ export const defaultTimelineProps: CreateTimelineProps = { highlightedDropAndProviderId: '', historyIds: [], id: TimelineId.active, + indexNames: [], isFavorite: false, isLive: false, isLoading: false, @@ -2262,7 +2259,7 @@ export const defaultTimelineProps: CreateTimelineProps = { ruleNote: '# this is some markdown documentation', }; -export const mockTimelineDetails: DetailItem[] = [ +export const mockTimelineDetails: TimelineEventsDetailsItem[] = [ { field: 'host.name', values: ['apache'], diff --git a/x-pack/plugins/security_solution/public/common/store/actions.ts b/x-pack/plugins/security_solution/public/common/store/actions.ts index 6b446ab6692d9..f4134b5c47c2c 100644 --- a/x-pack/plugins/security_solution/public/common/store/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/actions.ts @@ -12,6 +12,7 @@ import { TrustedAppsPageAction } from '../../management/pages/trusted_apps/store export { appActions } from './app'; export { dragAndDropActions } from './drag_and_drop'; export { inputsActions } from './inputs'; +export { sourcererActions } from './sourcerer'; import { RoutingAction } from './routing'; export type AppAction = diff --git a/x-pack/plugins/security_solution/public/common/store/model.ts b/x-pack/plugins/security_solution/public/common/store/model.ts index 0032a95cce321..04603d0607583 100644 --- a/x-pack/plugins/security_solution/public/common/store/model.ts +++ b/x-pack/plugins/security_solution/public/common/store/model.ts @@ -7,4 +7,5 @@ export { appModel } from './app'; export { dragAndDropModel } from './drag_and_drop'; export { inputsModel } from './inputs'; +export { sourcererModel } from './sourcerer'; export * from './types'; diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.ts b/x-pack/plugins/security_solution/public/common/store/reducer.ts index a0977cea71da7..60cb6a4e960bd 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/reducer.ts @@ -9,6 +9,7 @@ import { combineReducers, PreloadedState, AnyAction, Reducer } from 'redux'; import { appReducer, initialAppState } from './app'; import { dragAndDropReducer, initialDragAndDropState } from './drag_and_drop'; import { createInitialInputsState, inputsReducer } from './inputs'; +import { sourcererReducer, sourcererModel } from './sourcerer'; import { HostsPluginReducer } from '../../hosts/store'; import { NetworkPluginReducer } from '../../network/store'; @@ -18,6 +19,7 @@ import { SecuritySubPlugins } from '../../app/types'; import { ManagementPluginReducer } from '../../management'; import { State } from './types'; import { AppAction } from './actions'; +import { KibanaIndexPatterns } from './sourcerer/model'; export type SubPluginsInitReducer = HostsPluginReducer & NetworkPluginReducer & @@ -28,13 +30,22 @@ export type SubPluginsInitReducer = HostsPluginReducer & * Factory for the 'initialState' that is used to preload state into the Security App's redux store. */ export const createInitialState = ( - pluginsInitState: SecuritySubPlugins['store']['initialState'] + pluginsInitState: SecuritySubPlugins['store']['initialState'], + { + kibanaIndexPatterns, + configIndexPatterns, + }: { kibanaIndexPatterns: KibanaIndexPatterns; configIndexPatterns: string[] } ): PreloadedState => { const preloadedState: PreloadedState = { app: initialAppState, dragAndDrop: initialDragAndDropState, ...pluginsInitState, inputs: createInitialInputsState(), + sourcerer: { + ...sourcererModel.initialSourcererState, + kibanaIndexPatterns, + configIndexPatterns, + }, }; return preloadedState; }; @@ -49,5 +60,6 @@ export const createReducer: ( app: appReducer, dragAndDrop: dragAndDropReducer, inputs: inputsReducer, + sourcerer: sourcererReducer, ...pluginsReducer, }); diff --git a/x-pack/plugins/security_solution/public/common/store/selectors.ts b/x-pack/plugins/security_solution/public/common/store/selectors.ts index b938bae39b634..3cefd92bf9e60 100644 --- a/x-pack/plugins/security_solution/public/common/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/common/store/selectors.ts @@ -7,3 +7,4 @@ export { appSelectors } from './app'; export { dragAndDropSelectors } from './drag_and_drop'; export { inputsSelectors } from './inputs'; +export { sourcererSelectors } from './sourcerer'; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts new file mode 100644 index 0000000000000..0b40586798f09 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import actionCreatorFactory from 'typescript-fsa'; +import { TimelineEventsType } from '../../../../common/types/timeline'; + +import { KibanaIndexPatterns, ManageScopeInit, SourcererScopeName } from './model'; + +const actionCreator = actionCreatorFactory('x-pack/security_solution/local/sourcerer'); + +export const setSource = actionCreator<{ + id: SourcererScopeName; + payload: ManageScopeInit; +}>('SET_SOURCE'); + +export const setIndexPatternsList = actionCreator<{ + kibanaIndexPatterns: KibanaIndexPatterns; + configIndexPatterns: string[]; +}>('SET_INDEX_PATTERNS_LIST'); + +export const setSignalIndexName = actionCreator<{ signalIndexName: string }>( + 'SET_SIGNAL_INDEX_NAME' +); + +export const setSourcererScopeLoading = actionCreator<{ id: SourcererScopeName; loading: boolean }>( + 'SET_SOURCERER_SCOPE_LOADING' +); + +export const setSelectedIndexPatterns = actionCreator<{ + id: SourcererScopeName; + selectedPatterns: string[]; + eventType?: TimelineEventsType; +}>('SET_SELECTED_INDEX_PATTERNS'); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/index.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/index.ts new file mode 100644 index 0000000000000..551c7d8e3efbc --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as sourcererActions from './actions'; +import * as sourcererModel from './model'; +import * as sourcererSelectors from './selectors'; + +export { sourcererActions, sourcererModel, sourcererSelectors }; +export * from './reducer'; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts new file mode 100644 index 0000000000000..93f7ff95dfb00 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; +import { DocValueFields } from '../../../../common/search_strategy/common'; +import { + BrowserFields, + EMPTY_BROWSER_FIELDS, + EMPTY_DOCVALUE_FIELD, + EMPTY_INDEX_PATTERN, +} from '../../../../common/search_strategy/index_fields'; + +export type ErrorModel = Error[]; + +export enum SourcererScopeName { + default = 'default', + detections = 'detections', + timeline = 'timeline', +} + +export interface ManageScope { + browserFields: BrowserFields; + docValueFields: DocValueFields[]; + errorMessage: string | null; + id: SourcererScopeName; + indexPattern: IIndexPattern; + indicesExist: boolean | undefined | null; + loading: boolean; + selectedPatterns: string[]; +} + +export interface ManageScopeInit extends Partial { + id: SourcererScopeName; +} + +export type SourcererScopeById = { + [id in SourcererScopeName]: ManageScope; +}; + +export type KibanaIndexPatterns = Array<{ id: string; title: string }>; + +// ManageSourcerer +export interface SourcererModel { + kibanaIndexPatterns: KibanaIndexPatterns; + configIndexPatterns: string[]; + signalIndexName: string | null; + sourcererScopes: SourcererScopeById; +} + +export const initSourcererScope = { + browserFields: EMPTY_BROWSER_FIELDS, + docValueFields: EMPTY_DOCVALUE_FIELD, + errorMessage: null, + indexPattern: EMPTY_INDEX_PATTERN, + indicesExist: true, + loading: true, + selectedPatterns: [], +}; + +export const initialSourcererState: SourcererModel = { + kibanaIndexPatterns: [], + configIndexPatterns: [], + signalIndexName: null, + sourcererScopes: { + [SourcererScopeName.default]: { + ...initSourcererScope, + id: SourcererScopeName.default, + }, + [SourcererScopeName.detections]: { + ...initSourcererScope, + id: SourcererScopeName.detections, + }, + [SourcererScopeName.timeline]: { + ...initSourcererScope, + id: SourcererScopeName.timeline, + }, + }, +}; + +export type FSourcererScopePatterns = { + [id in SourcererScopeName]: string[]; +}; +export type SourcererScopePatterns = Partial; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts new file mode 100644 index 0000000000000..221244aaf9200 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports +import isEmpty from 'lodash/isEmpty'; +import { reducerWithInitialState } from 'typescript-fsa-reducers'; + +import { + setIndexPatternsList, + setSourcererScopeLoading, + setSelectedIndexPatterns, + setSignalIndexName, + setSource, +} from './actions'; +import { initialSourcererState, SourcererModel, SourcererScopeName } from './model'; + +export type SourcererState = SourcererModel; + +export const sourcererReducer = reducerWithInitialState(initialSourcererState) + .case(setIndexPatternsList, (state, { kibanaIndexPatterns, configIndexPatterns }) => ({ + ...state, + kibanaIndexPatterns, + configIndexPatterns, + })) + .case(setSignalIndexName, (state, { signalIndexName }) => ({ + ...state, + signalIndexName, + })) + .case(setSourcererScopeLoading, (state, { id, loading }) => ({ + ...state, + sourcererScopes: { + ...state.sourcererScopes, + [id]: { + ...state.sourcererScopes[id], + loading, + }, + }, + })) + .case(setSelectedIndexPatterns, (state, { id, selectedPatterns, eventType }) => { + const kibanaIndexPatterns = state.kibanaIndexPatterns.map((kip) => kip.title); + const newSelectedPatterns = selectedPatterns.filter( + (sp) => + state.configIndexPatterns.includes(sp) || + kibanaIndexPatterns.includes(sp) || + (!isEmpty(state.signalIndexName) && state.signalIndexName === sp) + ); + let defaultIndexPatterns = state.configIndexPatterns; + if (id === SourcererScopeName.timeline && isEmpty(newSelectedPatterns)) { + if (eventType === 'all' && !isEmpty(state.signalIndexName)) { + defaultIndexPatterns = [...state.configIndexPatterns, state.signalIndexName ?? '']; + } else if (eventType === 'raw') { + defaultIndexPatterns = state.configIndexPatterns; + } else if ( + !isEmpty(state.signalIndexName) && + (eventType === 'signal' || eventType === 'alert') + ) { + defaultIndexPatterns = [state.signalIndexName ?? '']; + } + } else if (id === SourcererScopeName.detections && isEmpty(newSelectedPatterns)) { + defaultIndexPatterns = [state.signalIndexName ?? '']; + } + return { + ...state, + sourcererScopes: { + ...state.sourcererScopes, + [id]: { + ...state.sourcererScopes[id], + selectedPatterns: isEmpty(newSelectedPatterns) + ? defaultIndexPatterns + : newSelectedPatterns, + }, + }, + }; + }) + .case(setSource, (state, { id, payload }) => { + const { ...sourcererScopes } = payload; + return { + ...state, + sourcererScopes: { + ...state.sourcererScopes, + [id]: { + ...state.sourcererScopes[id], + ...sourcererScopes, + ...(state.sourcererScopes[id].selectedPatterns.length === 0 + ? { selectedPatterns: state.configIndexPatterns } + : {}), + }, + }, + }; + }) + .build(); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts new file mode 100644 index 0000000000000..ca9ea26ba5bac --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createSelector } from 'reselect'; +import { State } from '../types'; +import { SourcererScopeById, KibanaIndexPatterns, SourcererScopeName, ManageScope } from './model'; + +export const sourcererKibanaIndexPatternsSelector = ({ sourcerer }: State): KibanaIndexPatterns => + sourcerer.kibanaIndexPatterns; + +export const sourcererSignalIndexNameSelector = ({ sourcerer }: State): string | null => + sourcerer.signalIndexName; + +export const sourcererConfigIndexPatternsSelector = ({ sourcerer }: State): string[] => + sourcerer.configIndexPatterns; + +export const sourcererScopesSelector = ({ sourcerer }: State): SourcererScopeById => + sourcerer.sourcererScopes; + +export const scopesSelector = () => createSelector(sourcererScopesSelector, (scopes) => scopes); + +export const kibanaIndexPatternsSelector = () => + createSelector( + sourcererKibanaIndexPatternsSelector, + (kibanaIndexPatterns) => kibanaIndexPatterns + ); + +export const signalIndexNameSelector = () => + createSelector(sourcererSignalIndexNameSelector, (signalIndexName) => signalIndexName); + +export const configIndexPatternsSelector = () => + createSelector( + sourcererConfigIndexPatternsSelector, + (configIndexPatterns) => configIndexPatterns + ); + +export const getIndexNamesSelectedSelector = () => { + const getScopesSelector = scopesSelector(); + const getConfigIndexPatternsSelector = configIndexPatternsSelector(); + + const mapStateToProps = (state: State, scopeId: SourcererScopeName): string[] => { + const scope = getScopesSelector(state)[scopeId]; + const configIndexPatterns = getConfigIndexPatternsSelector(state); + + return scope.selectedPatterns.length === 0 ? configIndexPatterns : scope.selectedPatterns; + }; + + return mapStateToProps; +}; + +export const getAllExistingIndexNamesSelector = () => { + const getSignalIndexNameSelector = signalIndexNameSelector(); + const getConfigIndexPatternsSelector = configIndexPatternsSelector(); + + const mapStateToProps = (state: State): string[] => { + const signalIndexName = getSignalIndexNameSelector(state); + const configIndexPatterns = getConfigIndexPatternsSelector(state); + + return signalIndexName != null + ? [...configIndexPatterns, signalIndexName] + : configIndexPatterns; + }; + + return mapStateToProps; +}; + +export const defaultIndexNamesSelector = () => { + const getScopesSelector = scopesSelector(); + const getConfigIndexPatternsSelector = configIndexPatternsSelector(); + + const mapStateToProps = (state: State, scopeId: SourcererScopeName): string[] => { + const scope = getScopesSelector(state)[scopeId]; + const configIndexPatterns = getConfigIndexPatternsSelector(state); + + return scope.selectedPatterns.length === 0 ? configIndexPatterns : scope.selectedPatterns; + }; + + return mapStateToProps; +}; + +export const getSourcererScopeSelector = () => { + const getScopesSelector = scopesSelector(); + + const mapStateToProps = (state: State, scopeId: SourcererScopeName): ManageScope => + getScopesSelector(state)[scopeId]; + + return mapStateToProps; +}; diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts index 91d92e4758c4a..6903567c752bc 100644 --- a/x-pack/plugins/security_solution/public/common/store/types.ts +++ b/x-pack/plugins/security_solution/public/common/store/types.ts @@ -12,6 +12,7 @@ import { AppAction } from './actions'; import { Immutable } from '../../../common/endpoint/types'; import { AppState } from './app/reducer'; import { InputsState } from './inputs/reducer'; +import { SourcererState } from './sourcerer/reducer'; import { HostsPluginState } from '../../hosts/store'; import { DragAndDropState } from './drag_and_drop/reducer'; import { TimelinePluginState } from '../../timelines/store/timeline'; @@ -25,6 +26,7 @@ export type StoreState = HostsPluginState & app: AppState; dragAndDrop: DragAndDropState; inputs: InputsState; + sourcerer: SourcererState; }; /** * The redux `State` type for the Security App. diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index 678aaf06e50e4..f326d5ad54ef2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -15,10 +15,12 @@ import { apolloClient, mockTimelineApolloResult, mockTimelineDetailsApollo, + mockTimelineDetails, } from '../../../common/mock/'; import { CreateTimeline, UpdateTimelineLoading } from './types'; import { Ecs } from '../../../../common/ecs'; import { TimelineId, TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { ISearchStart } from '../../../../../../../src/plugins/data/public'; jest.mock('apollo-client'); @@ -27,6 +29,7 @@ describe('alert actions', () => { const unix = moment(anchor).valueOf(); let createTimeline: CreateTimeline; let updateTimelineIsLoading: UpdateTimelineLoading; + let searchStrategyClient: ISearchStart; let clock: sinon.SinonFakeTimers; beforeEach(() => { @@ -39,6 +42,11 @@ describe('alert actions', () => { createTimeline = jest.fn() as jest.Mocked; updateTimelineIsLoading = jest.fn() as jest.Mocked; + searchStrategyClient = { + aggs: {} as ISearchStart['aggs'], + search: jest.fn().mockResolvedValue({ data: mockTimelineDetails }), + searchSource: {} as ISearchStart['searchSource'], + }; jest.spyOn(apolloClient, 'query').mockImplementation((obj) => { const id = get('variables.id', obj); @@ -64,6 +72,7 @@ describe('alert actions', () => { ecsData: mockEcsDataWithAlert, nonEcsData: [], updateTimelineIsLoading, + searchStrategyClient, }); expect(updateTimelineIsLoading).toHaveBeenCalledTimes(1); @@ -80,6 +89,7 @@ describe('alert actions', () => { ecsData: mockEcsDataWithAlert, nonEcsData: [], updateTimelineIsLoading, + searchStrategyClient, }); const expected = { from: '2018-11-05T18:58:25.937Z', @@ -197,6 +207,7 @@ describe('alert actions', () => { highlightedDropAndProviderId: '', historyIds: [], id: '', + indexNames: [], isFavorite: false, isLive: false, isLoading: false, @@ -267,6 +278,7 @@ describe('alert actions', () => { ecsData: mockEcsDataWithAlert, nonEcsData: [], updateTimelineIsLoading, + searchStrategyClient, }); const createTimelineArg = (createTimeline as jest.Mock).mock.calls[0][0]; @@ -296,6 +308,7 @@ describe('alert actions', () => { ecsData: mockEcsDataWithAlert, nonEcsData: [], updateTimelineIsLoading, + searchStrategyClient, }); const createTimelineArg = (createTimeline as jest.Mock).mock.calls[0][0]; @@ -314,6 +327,7 @@ describe('alert actions', () => { ecsData: mockEcsDataWithAlert, nonEcsData: [], updateTimelineIsLoading, + searchStrategyClient, }); expect(updateTimelineIsLoading).toHaveBeenCalledWith({ @@ -348,6 +362,7 @@ describe('alert actions', () => { ecsData: ecsDataMock, nonEcsData: [], updateTimelineIsLoading, + searchStrategyClient, }); expect(updateTimelineIsLoading).not.toHaveBeenCalled(); @@ -373,6 +388,7 @@ describe('alert actions', () => { ecsData: ecsDataMock, nonEcsData: [], updateTimelineIsLoading, + searchStrategyClient, }); expect(updateTimelineIsLoading).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 640726bb2e7c8..0e2aee5abd42e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -15,10 +15,13 @@ import { TimelineId, TimelineStatus, TimelineType } from '../../../../common/typ import { updateAlertStatus } from '../../containers/detection_engine/alerts/api'; import { SendAlertToTimelineActionProps, UpdateAlertStatusActionProps } from './types'; import { Ecs } from '../../../../common/ecs'; -import { GetOneTimeline, TimelineResult, GetTimelineDetailsQuery } from '../../../graphql/types'; +import { GetOneTimeline, TimelineResult } from '../../../graphql/types'; import { TimelineNonEcsData, TimelineEventsDetailsItem, + TimelineEventsDetailsRequestOptions, + TimelineEventsDetailsStrategyResponse, + TimelineEventsQueries, } from '../../../../common/search_strategy/timeline'; import { oneTimelineQuery } from '../../../timelines/containers/one/index.gql_query'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; @@ -34,7 +37,6 @@ import { } from './helpers'; import { KueryFilterQueryKind } from '../../../common/store'; import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; -import { timelineDetailsQuery } from '../../../timelines/containers/details/index.gql_query'; export const getUpdateAlertsQuery = (eventIds: Readonly) => { return { @@ -154,6 +156,7 @@ export const sendAlertToTimelineAction = async ({ ecsData, nonEcsData, updateTimelineIsLoading, + searchStrategyClient, }: SendAlertToTimelineActionProps) => { let openAlertInBasicTimeline = true; const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : ''; @@ -172,24 +175,24 @@ export const sendAlertToTimelineAction = async ({ id: timelineId, }, }), - apolloClient.query({ - query: timelineDetailsQuery, - fetchPolicy: 'no-cache', - variables: { + searchStrategyClient.search< + TimelineEventsDetailsRequestOptions, + TimelineEventsDetailsStrategyResponse + >( + { defaultIndex: [], docValueFields: [], - eventId: ecsData._id, indexName: ecsData._index ?? '', - sourceId: 'default', + eventId: ecsData._id, + factoryQueryType: TimelineEventsQueries.details, }, - }), + { + strategy: 'securitySolutionTimelineSearchStrategy', + } + ), ]); const resultingTimeline: TimelineResult = getOr({}, 'data.getOneTimeline', responseTimeline); - const eventData: TimelineEventsDetailsItem[] = getOr( - [], - 'data.source.TimelineDetails.data', - eventDataResp - ); + const eventData: TimelineEventsDetailsItem[] = getOr([], 'data', eventDataResp); if (!isEmpty(resultingTimeline)) { const timelineTemplate: TimelineResult = omitTypenameInTimeline(resultingTimeline); openAlertInBasicTimeline = false; @@ -279,6 +282,7 @@ export const sendAlertToTimelineAction = async ({ ...getThresholdAggregationDataProvider(ecsData, nonEcsData), ], id: TimelineId.active, + indexNames: [], dateRange: { start: from, end: to, @@ -329,6 +333,7 @@ export const sendAlertToTimelineAction = async ({ }, ], id: TimelineId.active, + indexNames: [], dateRange: { start: from, end: to, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts index 20c233a03a8cf..b386ce0c9631b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts @@ -11,7 +11,8 @@ import { DataProviderType, DataProvidersAnd, } from '../../../timelines/components/timeline/data_providers/data_provider'; -import { DetailItem, TimelineType } from '../../../graphql/types'; +import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; +import { TimelineType } from '../../../graphql/types'; interface FindValueToChangeInQuery { field: string; @@ -49,7 +50,7 @@ const templateFields = [ */ export const getStringArray = ( field: string, - data: DetailItem[], + data: TimelineEventsDetailsItem[], localConsole = console ): string[] => { const value: unknown | undefined = data.find((d) => d.field === field)?.values ?? null; @@ -108,7 +109,7 @@ export const findValueToChangeInQuery = ( export const replaceTemplateFieldFromQuery = ( query: string, - eventData: DetailItem[], + eventData: TimelineEventsDetailsItem[], timelineType: TimelineType = TimelineType.default ): string => { if (timelineType === TimelineType.default) { @@ -132,7 +133,7 @@ export const replaceTemplateFieldFromQuery = ( export const replaceTemplateFieldFromMatchFilters = ( filters: Filter[], - eventData: DetailItem[] + eventData: TimelineEventsDetailsItem[] ): Filter[] => filters.map((filter) => { if ( @@ -151,7 +152,7 @@ export const replaceTemplateFieldFromMatchFilters = ( export const reformatDataProviderWithNewValue = ( dataProvider: T, - eventData: DetailItem[], + eventData: TimelineEventsDetailsItem[], timelineType: TimelineType = TimelineType.default ): T => { // Support for legacy "template-like" timeline behavior that is using hardcoded list of templateFields @@ -201,7 +202,7 @@ export const reformatDataProviderWithNewValue = dataProviders.map((dataProvider) => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx index be24957602037..6724d3a83d617 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx @@ -22,7 +22,6 @@ describe('AlertsTableComponent', () => { hasIndexWrite from={'2020-07-07T08:20:18.966Z'} loading - signalsIndex="index" to={'2020-07-08T08:20:18.966Z'} globalQuery={{ query: 'query', diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 0416b3d2a459f..d66d37a020040 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -13,7 +13,6 @@ import { Status } from '../../../../common/detection_engine/schemas/common/schem import { Filter, esQuery } from '../../../../../../../src/plugins/data/public'; import { TimelineIdLiteral } from '../../../../common/types/timeline'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; -import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/fetch_index_patterns'; import { StatefulEventsViewer } from '../../../common/components/events_viewer'; import { HeaderSection } from '../../../common/components/header_section'; import { combineQueries } from '../../../timelines/components/timeline/helpers'; @@ -45,6 +44,8 @@ import { displaySuccessToast, displayErrorToast, } from '../../../common/components/toasters'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; interface OwnProps { timelineId: TimelineIdLiteral; @@ -55,7 +56,6 @@ interface OwnProps { loading: boolean; showBuildingBlockAlerts: boolean; onShowBuildingBlockAlertsChanged: (showBuildingBlockAlerts: boolean) => void; - signalsIndex: string; to: string; } @@ -80,19 +80,20 @@ export const AlertsTableComponent: React.FC = ({ setEventsLoading, showBuildingBlockAlerts, onShowBuildingBlockAlertsChanged, - signalsIndex, to, }) => { const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); - const [{ browserFields, indexPatterns, isLoading: indexPatternsLoading }] = useFetchIndexPatterns( - signalsIndex !== '' ? [signalsIndex] : [], - 'alerts_table' - ); + const { + browserFields, + indexPattern: indexPatterns, + loading: indexPatternsLoading, + selectedPatterns, + } = useSourcererScope(SourcererScopeName.detections); const kibana = useKibana(); const [, dispatchToaster] = useStateToaster(); const { addWarning } = useAppToasts(); - const { initializeTimeline, setSelectAll, setIndexToAdd } = useManageTimeline(); + const { initializeTimeline, setSelectAll } = useManageTimeline(); const getGlobalQuery = useCallback( (customFilters: Filter[]) => { @@ -284,7 +285,6 @@ export const AlertsTableComponent: React.FC = ({ ] ); - const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); const defaultFiltersMemo = useMemo(() => { if (isEmpty(defaultFilters)) { return buildAlertStatusFilter(filterGroup); @@ -301,7 +301,6 @@ export const AlertsTableComponent: React.FC = ({ filterManager, footerText: i18n.TOTAL_COUNT_OF_ALERTS, id: timelineId, - indexToAdd: defaultIndices, loadingText: i18n.LOADING_ALERTS, selectAll: false, queryFields: requiredFieldsForActions, @@ -310,16 +309,12 @@ export const AlertsTableComponent: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - setIndexToAdd({ id: timelineId, indexToAdd: defaultIndices }); - }, [timelineId, defaultIndices, setIndexToAdd]); - const headerFilterGroup = useMemo( () => , [onFilterGroupChangedCallback] ); - if (loading || indexPatternsLoading || isEmpty(signalsIndex)) { + if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) { return ( @@ -330,12 +325,12 @@ export const AlertsTableComponent: React.FC = ({ return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 4559e44b8c3c5..82fed152ea66d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -109,7 +109,7 @@ const AlertContextMenuComponent: React.FC = ({ const closeAddExceptionModal = useCallback(() => { setShouldShowAddExceptionModal(false); setAddExceptionModalState(addExceptionModalInitialState); - }, [setShouldShowAddExceptionModal, setAddExceptionModalState]); + }, []); const onAddExceptionCancel = useCallback(() => { closeAddExceptionModal(); @@ -305,33 +305,6 @@ const AlertContextMenuComponent: React.FC = ({ [setShouldShowAddExceptionModal, setAddExceptionModalState] ); - const AddExceptionModal = useCallback( - () => - shouldShowAddExceptionModal === true && addExceptionModalState.alertData !== null ? ( - - ) : null, - [ - shouldShowAddExceptionModal, - addExceptionModalState.alertData, - addExceptionModalState.ruleName, - addExceptionModalState.ruleId, - addExceptionModalState.ruleIndices, - addExceptionModalState.exceptionListType, - onAddExceptionCancel, - onAddExceptionConfirm, - alertStatus, - ] - ); - const button = ( = ({
- + {shouldShowAddExceptionModal === true && addExceptionModalState.alertData !== null && ( + + )} ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx index 4ab5fa5e6012f..8960b7a76660b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx @@ -7,6 +7,7 @@ import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; +import { useKibana } from '../../../../common/lib/kibana'; import { TimelineId } from '../../../../../common/types/timeline'; import { Ecs } from '../../../../../common/ecs'; import { TimelineNonEcsData } from '../../../../../common/search_strategy/timeline'; @@ -15,7 +16,6 @@ import { useApolloClient } from '../../../../common/utils/apollo_context'; import { sendAlertToTimelineAction } from '../actions'; import { dispatchUpdateTimeline } from '../../../../timelines/components/open_timeline/helpers'; import { ActionIconItem } from '../../../../timelines/components/timeline/body/actions/action_icon_item'; - import { CreateTimelineProps } from '../types'; import { ACTION_INVESTIGATE_IN_TIMELINE, @@ -31,6 +31,9 @@ const InvestigateInTimelineActionComponent: React.FC { + const { + data: { search: searchStrategyClient }, + } = useKibana().services; const dispatch = useDispatch(); const apolloClient = useApolloClient(); @@ -49,6 +52,8 @@ const InvestigateInTimelineActionComponent: React.FC void; diff --git a/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx b/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx index 78a18dc336e5b..1a2deb059ad4f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx @@ -10,7 +10,7 @@ import { HeaderPage, HeaderPageProps } from '../../../common/components/header_p import * as i18n from './translations'; const DetectionEngineHeaderPageComponent: React.FC = (props) => ( - + ); DetectionEngineHeaderPageComponent.defaultProps = { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx index f2eb5cf5b94f3..2ce9d1ea68b3c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx @@ -13,6 +13,7 @@ import { esFilters, FilterManager, UI_SETTINGS, + IndexPattern, } from '../../../../../../../../src/plugins/data/public'; import { SeverityBadge } from '../severity_badge'; @@ -140,11 +141,11 @@ describe('helpers', () => { filterManager: mockFilterManager, query: mockQueryBarWithFilters.query, savedId: mockQueryBarWithFilters.saved_id, - indexPatterns: { + indexPatterns: ({ fields: [{ name: 'event.category', type: 'test type' }], title: 'test title', getFormatterForField: () => ({ convert: (val: unknown) => val }), - }, + } as unknown) as IndexPattern, }); const wrapper = shallow(result[0].description as React.ReactElement); const filterLabelComponent = wrapper.find(esFilters.FilterLabel).at(0); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx index cb25785eaa5b2..4312be0b46990 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx @@ -8,8 +8,9 @@ import { mount, shallow } from 'enzyme'; import { ThemeProvider } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_light.json'; +import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_pattern.stub'; import { StepAboutRule } from '.'; - +import { useFetchIndex } from '../../../../common/containers/source'; import { mockAboutStepRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; import { StepRuleDescription } from '../description_step'; import { stepAboutDefaultValue } from './default_value'; @@ -20,6 +21,7 @@ import { } from '../../../pages/detection_engine/rules/types'; import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers'; +jest.mock('../../../../common/containers/source'); const theme = () => ({ eui: euiDarkVars, darkMode: true }); /* eslint-disable no-console */ @@ -44,6 +46,12 @@ describe('StepAboutRuleComponent', () => { beforeEach(() => { formHook = null; + (useFetchIndex as jest.Mock).mockImplementation(() => [ + false, + { + indexPatterns: stubIndexPattern, + }, + ]); }); test('it renders StepRuleDescription if isReadOnlyView is true and "name" property exists', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx index 66f95f5ce15d2..90b70e53a459e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx @@ -39,8 +39,8 @@ import { NextStep } from '../next_step'; import { MarkdownEditorForm } from '../../../../common/components/markdown_editor/eui_form'; import { SeverityField } from '../severity_mapping'; import { RiskScoreField } from '../risk_score_mapping'; -import { useFetchIndexPatterns } from '../../../containers/detection_engine/rules'; import { AutocompleteField } from '../autocomplete_field'; +import { useFetchIndex } from '../../../../common/containers/source'; const CommonUseField = getUseField({ component: Field }); @@ -74,10 +74,8 @@ const StepAboutRuleComponent: FC = ({ }) => { const initialState = defaultValues ?? stepAboutDefaultValue; const [severityValue, setSeverityValue] = useState(initialState.severity.value); - const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns( - defineRuleData?.index ?? [], - RuleStep.aboutRule - ); + const [indexPatternLoading, { indexPatterns }] = useFetchIndex(defineRuleData?.index ?? []); + const canUseExceptions = defineRuleData?.ruleType && !isMlRule(defineRuleData.ruleType) && diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 7846f0c406668..dc31db76c3911 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -7,6 +7,8 @@ import { EuiButtonEmpty, EuiFormRow } from '@elastic/eui'; import React, { FC, memo, useCallback, useState, useEffect } from 'react'; import styled from 'styled-components'; +// Prefer importing entire lodash library, e.g. import { get } from "lodash" +// eslint-disable-next-line no-restricted-imports import isEqual from 'lodash/isEqual'; import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; @@ -14,7 +16,6 @@ import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timelin import { isMlRule } from '../../../../../common/machine_learning/helpers'; import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license'; -import { useFetchIndexPatterns } from '../../../containers/detection_engine/rules'; import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; import { useUiSetting$ } from '../../../../common/lib/kibana'; import { @@ -48,6 +49,7 @@ import { schema } from './schema'; import * as i18n from './translations'; import { isEqlRule, isThresholdRule } from '../../../../../common/detection_engine/utils'; import { EqlQueryBar } from '../eql_query_bar'; +import { useFetchIndex } from '../../../../common/containers/source'; const CommonUseField = getUseField({ component: Field }); @@ -125,10 +127,7 @@ const StepDefineRuleComponent: FC = ({ }) as unknown) as [Partial]; const index = formIndex || initialState.index; const ruleType = formRuleType || initialState.ruleType; - const [{ browserFields, indexPatterns, isLoading: indexPatternsLoading }] = useFetchIndexPatterns( - index, - RuleStep.defineRule - ); + const [indexPatternsLoading, { browserFields, indexPatterns }] = useFetchIndex(index); // reset form when rule type changes useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx index e1a29c3575d95..00e108ffb89b6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx @@ -169,22 +169,19 @@ export const useUserInfo = (): State => { if (loading !== privilegeLoading || indexNameLoading) { dispatch({ type: 'updateLoading', loading: privilegeLoading || indexNameLoading }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, privilegeLoading, indexNameLoading]); + }, [dispatch, loading, privilegeLoading, indexNameLoading]); useEffect(() => { if (!loading && hasIndexManage !== hasApiIndexManage && hasApiIndexManage != null) { dispatch({ type: 'updateHasIndexManage', hasIndexManage: hasApiIndexManage }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, hasIndexManage, hasApiIndexManage]); + }, [dispatch, loading, hasIndexManage, hasApiIndexManage]); useEffect(() => { if (!loading && hasIndexWrite !== hasApiIndexWrite && hasApiIndexWrite != null) { dispatch({ type: 'updateHasIndexWrite', hasIndexWrite: hasApiIndexWrite }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, hasIndexWrite, hasApiIndexWrite]); + }, [dispatch, loading, hasIndexWrite, hasApiIndexWrite]); useEffect(() => { if ( @@ -194,36 +191,31 @@ export const useUserInfo = (): State => { ) { dispatch({ type: 'updateIsSignalIndexExists', isSignalIndexExists: isApiSignalIndexExists }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, isSignalIndexExists, isApiSignalIndexExists]); + }, [dispatch, loading, isSignalIndexExists, isApiSignalIndexExists]); useEffect(() => { if (!loading && isAuthenticated !== isApiAuthenticated && isApiAuthenticated != null) { dispatch({ type: 'updateIsAuthenticated', isAuthenticated: isApiAuthenticated }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, isAuthenticated, isApiAuthenticated]); + }, [dispatch, loading, isAuthenticated, isApiAuthenticated]); useEffect(() => { if (!loading && hasEncryptionKey !== isApiEncryptionKey && isApiEncryptionKey != null) { dispatch({ type: 'updateHasEncryptionKey', hasEncryptionKey: isApiEncryptionKey }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, hasEncryptionKey, isApiEncryptionKey]); + }, [dispatch, loading, hasEncryptionKey, isApiEncryptionKey]); useEffect(() => { if (!loading && canUserCRUD !== capabilitiesCanUserCRUD && capabilitiesCanUserCRUD != null) { dispatch({ type: 'updateCanUserCRUD', canUserCRUD: capabilitiesCanUserCRUD }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, canUserCRUD, capabilitiesCanUserCRUD]); + }, [dispatch, loading, canUserCRUD, capabilitiesCanUserCRUD]); useEffect(() => { if (!loading && signalIndexName !== apiSignalIndexName && apiSignalIndexName != null) { dispatch({ type: 'updateSignalIndexName', signalIndexName: apiSignalIndexName }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, signalIndexName, apiSignalIndexName]); + }, [dispatch, loading, signalIndexName, apiSignalIndexName]); useEffect(() => { if ( diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/form.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/form.tsx index c35cc612129d5..5baa0ee5569d2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/form.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/form.tsx @@ -21,7 +21,7 @@ import { useImportList, ListSchema, Type } from '../../../shared_imports'; import * as i18n from './translations'; import { useKibana } from '../../../common/lib/kibana'; -const options: EuiSelectOption[] = [ +export const listFormOptions: EuiSelectOption[] = [ { value: 'keyword', text: i18n.KEYWORDS_RADIO, @@ -145,7 +145,7 @@ export const ValueListsFormComponent: React.FC = ({ onError theme.eui.euiSizeXS}; @@ -29,6 +30,16 @@ export const buildColumns = ( name: i18n.COLUMN_FILE_NAME, truncateText: true, }, + { + field: 'type', + name: i18n.COLUMN_TYPE, + width: '15%', + truncateText: true, + render: (type: ListSchema['type']) => { + const option = listFormOptions.find(({ value }) => value === type); + return <>{option ? option.text : type}; + }, + }, { field: 'created_at', name: i18n.COLUMN_UPLOAD_DATE, diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/translations.ts b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/translations.ts index 9beebdfb923dc..992c696121485 100644 --- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/translations.ts @@ -76,6 +76,13 @@ export const COLUMN_FILE_NAME = i18n.translate( } ); +export const COLUMN_TYPE = i18n.translate( + 'xpack.securitySolution.lists.valueListsTable.typeColumn', + { + defaultMessage: 'Type', + } +); + export const COLUMN_UPLOAD_DATE = i18n.translate( 'xpack.securitySolution.lists.valueListsTable.uploadDateColumn', { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx deleted file mode 100644 index d36c19a6a35c6..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx +++ /dev/null @@ -1,475 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { renderHook, act } from '@testing-library/react-hooks'; - -import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; -import { useApolloClient } from '../../../../common/utils/apollo_context'; -import { mocksSource } from '../../../../common/containers/source/mock'; - -import { useFetchIndexPatterns, Return } from './fetch_index_patterns'; - -const mockUseApolloClient = useApolloClient as jest.Mock; -jest.mock('../../../../common/utils/apollo_context'); - -describe('useFetchIndexPatterns', () => { - beforeEach(() => { - mockUseApolloClient.mockClear(); - }); - test('happy path', async () => { - await act(async () => { - mockUseApolloClient.mockImplementation(() => ({ - query: () => Promise.resolve(mocksSource[0].result), - })); - const { result, waitForNextUpdate } = renderHook(() => - useFetchIndexPatterns(DEFAULT_INDEX_PATTERN) - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - - expect(result.current).toEqual([ - { - browserFields: { - base: { - fields: { - '@timestamp': { - category: 'base', - description: - 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', - example: '2016-05-23T08:05:34.853Z', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: '@timestamp', - searchable: true, - type: 'date', - aggregatable: true, - }, - }, - }, - agent: { - fields: { - 'agent.ephemeral_id': { - category: 'agent', - description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.ephemeral_id', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'agent.hostname': { - category: 'agent', - description: null, - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.hostname', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'agent.id': { - category: 'agent', - description: - 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', - example: '8a4f500d', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'agent.name': { - category: 'agent', - description: - 'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', - example: 'foo', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.name', - searchable: true, - type: 'string', - aggregatable: true, - }, - }, - }, - auditd: { - fields: { - 'auditd.data.a0': { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a0', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'auditd.data.a1': { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a1', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'auditd.data.a2': { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a2', - searchable: true, - type: 'string', - aggregatable: true, - }, - }, - }, - client: { - fields: { - 'client.address': { - category: 'client', - description: - 'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.address', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'client.bytes': { - category: 'client', - description: 'Bytes sent from the client to the server.', - example: '184', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.bytes', - searchable: true, - type: 'number', - aggregatable: true, - }, - 'client.domain': { - category: 'client', - description: 'Client domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.domain', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'client.geo.country_iso_code': { - category: 'client', - description: 'Country ISO code.', - example: 'CA', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.geo.country_iso_code', - searchable: true, - type: 'string', - aggregatable: true, - }, - }, - }, - cloud: { - fields: { - 'cloud.account.id': { - category: 'cloud', - description: - 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.', - example: '666777888999', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'cloud.account.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'cloud.availability_zone': { - category: 'cloud', - description: 'Availability zone in which this host is running.', - example: 'us-east-1c', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'cloud.availability_zone', - searchable: true, - type: 'string', - aggregatable: true, - }, - }, - }, - container: { - fields: { - 'container.id': { - category: 'container', - description: 'Unique container id.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'container.image.name': { - category: 'container', - description: 'Name of the image the container was built on.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.image.name', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'container.image.tag': { - category: 'container', - description: 'Container image tag.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.image.tag', - searchable: true, - type: 'string', - aggregatable: true, - }, - }, - }, - destination: { - fields: { - 'destination.address': { - category: 'destination', - description: - 'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.address', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'destination.bytes': { - category: 'destination', - description: 'Bytes sent from the destination to the source.', - example: '184', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.bytes', - searchable: true, - type: 'number', - aggregatable: true, - }, - 'destination.domain': { - category: 'destination', - description: 'Destination domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.domain', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'destination.ip': { - aggregatable: true, - category: 'destination', - description: - 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.ip', - searchable: true, - type: 'ip', - }, - 'destination.port': { - aggregatable: true, - category: 'destination', - description: 'Port of the destination.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.port', - searchable: true, - type: 'long', - }, - }, - }, - source: { - fields: { - 'source.ip': { - aggregatable: true, - category: 'source', - description: - 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'source.ip', - searchable: true, - type: 'ip', - }, - 'source.port': { - aggregatable: true, - category: 'source', - description: 'Port of the source.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'source.port', - searchable: true, - type: 'long', - }, - }, - }, - event: { - fields: { - 'event.end': { - aggregatable: true, - category: 'event', - description: - 'event.end contains the date when the event ended or when the activity was last observed.', - example: null, - format: '', - indexes: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - name: 'event.end', - searchable: true, - type: 'date', - }, - }, - }, - }, - isLoading: false, - indices: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - indicesExists: true, - docValueFields: [ - { - field: '@timestamp', - format: 'date_time', - }, - { - field: 'event.end', - format: 'date_time', - }, - ], - indexPatterns: { - fields: [ - { name: '@timestamp', searchable: true, type: 'date', aggregatable: true }, - { name: 'agent.ephemeral_id', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.hostname', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.id', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.name', searchable: true, type: 'string', aggregatable: true }, - { name: 'auditd.data.a0', searchable: true, type: 'string', aggregatable: true }, - { name: 'auditd.data.a1', searchable: true, type: 'string', aggregatable: true }, - { name: 'auditd.data.a2', searchable: true, type: 'string', aggregatable: true }, - { name: 'client.address', searchable: true, type: 'string', aggregatable: true }, - { name: 'client.bytes', searchable: true, type: 'number', aggregatable: true }, - { name: 'client.domain', searchable: true, type: 'string', aggregatable: true }, - { - name: 'client.geo.country_iso_code', - searchable: true, - type: 'string', - aggregatable: true, - }, - { name: 'cloud.account.id', searchable: true, type: 'string', aggregatable: true }, - { - name: 'cloud.availability_zone', - searchable: true, - type: 'string', - aggregatable: true, - }, - { name: 'container.id', searchable: true, type: 'string', aggregatable: true }, - { - name: 'container.image.name', - searchable: true, - type: 'string', - aggregatable: true, - }, - { name: 'container.image.tag', searchable: true, type: 'string', aggregatable: true }, - { name: 'destination.address', searchable: true, type: 'string', aggregatable: true }, - { name: 'destination.bytes', searchable: true, type: 'number', aggregatable: true }, - { name: 'destination.domain', searchable: true, type: 'string', aggregatable: true }, - { name: 'destination.ip', searchable: true, type: 'ip', aggregatable: true }, - { name: 'destination.port', searchable: true, type: 'long', aggregatable: true }, - { name: 'source.ip', searchable: true, type: 'ip', aggregatable: true }, - { name: 'source.port', searchable: true, type: 'long', aggregatable: true }, - { name: 'event.end', searchable: true, type: 'date', aggregatable: true }, - ], - title: - 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*', - }, - }, - result.current[1], - ]); - }); - }); - - test('unhappy path', async () => { - await act(async () => { - mockUseApolloClient.mockImplementation(() => ({ - query: () => Promise.reject(new Error('Something went wrong')), - })); - const { result, waitForNextUpdate } = renderHook(() => - useFetchIndexPatterns(DEFAULT_INDEX_PATTERN) - ); - - await waitForNextUpdate(); - await waitForNextUpdate(); - - expect(result.current).toEqual([ - { - browserFields: {}, - docValueFields: [], - indexPatterns: { - fields: [], - title: '', - }, - indices: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - indicesExists: false, - isLoading: false, - }, - result.current[1], - ]); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx deleted file mode 100644 index 82c9292af7451..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx +++ /dev/null @@ -1,132 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { isEmpty, get } from 'lodash/fp'; -import { useEffect, useState, Dispatch, SetStateAction } from 'react'; -import deepEqual from 'fast-deep-equal'; - -import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; -import { - BrowserFields, - getBrowserFields, - getDocValueFields, - getIndexFields, - sourceQuery, - DocValueFields, -} from '../../../../common/containers/source'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; -import { SourceQuery } from '../../../../graphql/types'; -import { useApolloClient } from '../../../../common/utils/apollo_context'; - -import * as i18n from './translations'; - -interface FetchIndexPatternReturn { - browserFields: BrowserFields; - docValueFields: DocValueFields[]; - isLoading: boolean; - indices: string[]; - indicesExists: boolean; - indexPatterns: IIndexPattern; -} - -export type Return = [FetchIndexPatternReturn, Dispatch>]; - -const DEFAULT_BROWSER_FIELDS = {}; -const DEFAULT_INDEX_PATTERNS = { fields: [], title: '' }; -const DEFAULT_DOC_VALUE_FIELDS: DocValueFields[] = []; - -// Fun fact: When using this hook multiple times within a component (e.g. add_exception_modal & edit_exception_modal), -// the apolloClient will perform queryDeduplication and prevent the first query from executing. A deep compare is not -// performed on `indices`, so another field must be passed to circumvent this. -// For details, see https://github.com/apollographql/react-apollo/issues/2202 -export const useFetchIndexPatterns = ( - defaultIndices: string[] = [], - queryDeduplication?: string -): Return => { - const apolloClient = useApolloClient(); - const [indices, setIndices] = useState(defaultIndices); - - const [state, setState] = useState({ - browserFields: DEFAULT_BROWSER_FIELDS, - docValueFields: DEFAULT_DOC_VALUE_FIELDS, - indices: defaultIndices, - indicesExists: false, - indexPatterns: DEFAULT_INDEX_PATTERNS, - isLoading: false, - }); - - const [, dispatchToaster] = useStateToaster(); - - useEffect(() => { - if (!deepEqual(defaultIndices, indices)) { - setIndices(defaultIndices); - setState((prevState) => ({ ...prevState, indices: defaultIndices })); - } - }, [defaultIndices, indices]); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - - async function fetchIndexPatterns() { - if (apolloClient && !isEmpty(indices)) { - setState((prevState) => ({ ...prevState, isLoading: true })); - apolloClient - .query({ - query: sourceQuery, - fetchPolicy: 'cache-first', - variables: { - sourceId: 'default', - defaultIndex: indices, - ...(queryDeduplication != null ? { queryDeduplication } : {}), - }, - context: { - fetchOptions: { - signal: abortCtrl.signal, - }, - }, - }) - .then( - (result) => { - if (isSubscribed) { - setState({ - browserFields: getBrowserFields( - indices.join(), - get('data.source.status.indexFields', result) - ), - docValueFields: getDocValueFields( - indices.join(), - get('data.source.status.indexFields', result) - ), - indices, - isLoading: false, - indicesExists: get('data.source.status.indicesExist', result), - indexPatterns: getIndexFields( - indices.join(), - get('data.source.status.indexFields', result) - ), - }); - } - }, - (error) => { - if (isSubscribed) { - setState((prevState) => ({ ...prevState, isLoading: false })); - errorToToaster({ title: i18n.RULE_ADD_FAILURE, error, dispatchToaster }); - } - } - ); - } - } - fetchIndexPatterns(); - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [indices]); - - return [state, setIndices]; -}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts index a40ab2e487851..930391261ac87 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts @@ -5,7 +5,6 @@ */ export * from './api'; -export * from './fetch_index_patterns'; export * from './use_update_rule'; export * from './use_create_rule'; export * from './types'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index 8c21f6a1e8cb7..a5d21d2847586 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -20,7 +20,7 @@ import { import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; import { DetectionEnginePageComponent } from './detection_engine'; import { useUserData } from '../../components/user_info'; -import { useWithSource } from '../../../common/containers/source'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; import { createStore, State } from '../../../common/store'; import { mockHistory, Router } from '../../../cases/components/__mock__/router'; @@ -34,7 +34,7 @@ jest.mock('../../../common/components/query_bar', () => ({ })); jest.mock('../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../components/user_info'); -jest.mock('../../../common/containers/source'); +jest.mock('../../../common/containers/sourcerer'); jest.mock('../../../common/components/link_to'); jest.mock('../../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ @@ -74,7 +74,7 @@ describe('DetectionEnginePageComponent', () => { beforeAll(() => { (useParams as jest.Mock).mockReturnValue({}); (useUserData as jest.Mock).mockReturnValue([{}]); - (useWithSource as jest.Mock).mockReturnValue({ + (useSourcererScope as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 3a3854f145db3..b39cd37521602 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -13,7 +13,6 @@ import { useHistory } from 'react-router-dom'; import { SecurityPageName } from '../../../app/types'; import { TimelineId } from '../../../../common/types/timeline'; import { useGlobalTime } from '../../../common/containers/use_global_time'; -import { useWithSource } from '../../../common/containers/source'; import { UpdateDateRange } from '../../../common/components/charts/common'; import { FiltersGlobal } from '../../../common/components/filters_global'; import { getRulesUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; @@ -46,6 +45,8 @@ import { timelineSelectors } from '../../../timelines/store/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { TimelineModel } from '../../../timelines/store/timeline/model'; import { buildShowBuildingBlockFilter } from '../../components/alerts_table/default_config'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; export const DetectionEnginePageComponent: React.FC = ({ filters, @@ -117,10 +118,7 @@ export const DetectionEnginePageComponent: React.FC = ({ [setShowBuildingBlockAlerts] ); - const indexToAdd = useMemo(() => (signalIndexName == null ? [] : [signalIndexName]), [ - signalIndexName, - ]); - const { indicesExist, indexPattern } = useWithSource('default', indexToAdd); + const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections); if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { return ( @@ -202,7 +200,6 @@ export const DetectionEnginePageComponent: React.FC = ({ defaultFilters={alertsTableDefaultFilters} showBuildingBlockAlerts={showBuildingBlockAlerts} onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChangedCallback} - signalsIndex={signalIndexName ?? ''} to={to} /> diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx index f8f9da78b2a06..22c3c43fb2356 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx @@ -20,7 +20,7 @@ import { RuleDetailsPageComponent } from './index'; import { createStore, State } from '../../../../../common/store'; import { setAbsoluteRangeDatePicker } from '../../../../../common/store/inputs/actions'; import { useUserData } from '../../../../components/user_info'; -import { useWithSource } from '../../../../../common/containers/source'; +import { useSourcererScope } from '../../../../../common/containers/sourcerer'; import { useParams } from 'react-router-dom'; import { mockHistory, Router } from '../../../../../cases/components/__mock__/router'; @@ -35,7 +35,7 @@ jest.mock('../../../../../common/components/query_bar', () => ({ jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../components/user_info'); -jest.mock('../../../../../common/containers/source'); +jest.mock('../../../../../common/containers/sourcerer'); jest.mock('../../../../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ from: '2020-07-07T08:20:18.966Z', @@ -71,7 +71,7 @@ describe('RuleDetailsPageComponent', () => { beforeAll(() => { (useUserData as jest.Mock).mockReturnValue([{}]); (useParams as jest.Mock).mockReturnValue({}); - (useWithSource as jest.Mock).mockReturnValue({ + (useSourcererScope as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 68799f46eee57..ad8ab3ed3a148 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -36,10 +36,7 @@ import { SiemSearchBar } from '../../../../../common/components/search_bar'; import { WrapperPage } from '../../../../../common/components/wrapper_page'; import { Rule } from '../../../../containers/detection_engine/rules'; import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config'; - -import { useWithSource } from '../../../../../common/containers/source'; import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; - import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details'; import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page'; import { AlertsHistogramPanel } from '../../../../components/alerts_histogram_panel'; @@ -89,6 +86,9 @@ import { showGlobalFilters } from '../../../../../timelines/components/timeline/ import { timelineSelectors } from '../../../../../timelines/store/timeline'; import { timelineDefaults } from '../../../../../timelines/store/timeline/defaults'; import { TimelineModel } from '../../../../../timelines/store/timeline/model'; +import { useSourcererScope } from '../../../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; +import { AlertsHistogramOption } from '../../../../components/alerts_histogram_panel/types'; enum RuleDetailTabs { alerts = 'alerts', @@ -265,10 +265,6 @@ export const RuleDetailsPageComponent: FC = ({ [rule, ruleDetailTab] ); - const indexToAdd = useMemo(() => (signalIndexName == null ? [] : [signalIndexName]), [ - signalIndexName, - ]); - const updateDateRangeCallback = useCallback( ({ x }) => { if (!x) { @@ -308,7 +304,7 @@ export const RuleDetailsPageComponent: FC = ({ [setShowBuildingBlockAlerts] ); - const { indicesExist, indexPattern } = useWithSource('default', indexToAdd); + const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections); const exceptionLists = useMemo((): { lists: ExceptionIdentifiers[]; @@ -350,6 +346,11 @@ export const RuleDetailsPageComponent: FC = ({ return null; } + const defaultRuleStackByOption: AlertsHistogramOption = { + text: 'event.category', + value: 'event.category', + }; + return ( <> {hasIndexWrite != null && !hasIndexWrite && } @@ -485,6 +486,7 @@ export const RuleDetailsPageComponent: FC = ({ signalIndexName={signalIndexName} setQuery={setQuery} stackByOptions={alertsHistogramOptions} + defaultStackByOption={defaultRuleStackByOption} to={to} updateDateRange={updateDateRangeCallback} /> @@ -500,7 +502,6 @@ export const RuleDetailsPageComponent: FC = ({ loading={loading} showBuildingBlockAlerts={showBuildingBlockAlerts} onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChangedCallback} - signalsIndex={signalIndexName ?? ''} to={to} /> )} diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json index b32083fec1b5e..8d780137b847c 100644 --- a/x-pack/plugins/security_solution/public/graphql/introspection.json +++ b/x-pack/plugins/security_solution/public/graphql/introspection.json @@ -683,9 +683,15 @@ "deprecationReason": null }, { - "name": "Authentications", - "description": "Gets Authentication success and failures based on a timerange", + "name": "Hosts", + "description": "Gets Hosts based on timerange and specified criteria, or all events in the timerange if no criteria is specified", "args": [ + { + "name": "id", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, { "name": "timerange", "description": "", @@ -710,6 +716,16 @@ }, "defaultValue": null }, + { + "name": "sort", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "INPUT_OBJECT", "name": "HostsSortField", "ofType": null } + }, + "defaultValue": null + }, { "name": "filterQuery", "description": "", @@ -760,65 +776,41 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "AuthenticationsData", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "HostsData", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "Timeline", + "name": "HostOverview", "description": "", "args": [ { - "name": "pagination", + "name": "id", "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "PaginationInput", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "sortField", + "name": "hostName", "description": "", "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "SortField", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "defaultValue": null }, { - "name": "fieldRequested", + "name": "timerange", "description": "", "type": { "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } + "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } }, "defaultValue": null }, - { - "name": "timerange", - "description": "", - "type": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, { "name": "defaultIndex", "description": "", @@ -836,54 +828,28 @@ } }, "defaultValue": null - }, - { - "name": "docValueFields", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "docValueFieldsInput", - "ofType": null - } - } - } - }, - "defaultValue": null } ], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "TimelineData", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "HostItem", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "TimelineDetails", + "name": "HostFirstLastSeen", "description": "", "args": [ { - "name": "eventId", + "name": "id", "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "indexName", + "name": "hostName", "description": "", "type": { "kind": "NON_NULL", @@ -936,41 +902,140 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "TimelineDetailsData", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "FirstLastSeenHost", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SourceConfiguration", + "description": "A set of configuration options for a security data source", + "fields": [ + { + "name": "fields", + "description": "The field mapping to use for this source", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "SourceFields", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SourceFields", + "description": "A mapping of semantic fields to their document counterparts", + "fields": [ + { + "name": "container", + "description": "The field to identify a container by", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "LastEventTime", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "indexKey", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "LastEventIndexKey", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "details", - "description": "", - "type": { + "name": "host", + "description": "The fields to identify a host by", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "message", + "description": "The fields that may contain the log event message. The first field found win.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "LastTimeDetails", "ofType": null } - }, - "defaultValue": null - }, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pod", + "description": "The field to identify a pod by", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "tiebreaker", + "description": "The field to use as a tiebreaker for log events that have identical timestamps", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "timestamp", + "description": "The field to use as a timestamp for metrics and logs", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SourceStatus", + "description": "The status of an infrastructure data source", + "fields": [ + { + "name": "indicesExist", + "description": "Whether the configured alias or wildcard pattern resolve to any auditbeat indices", + "args": [ { "name": "defaultIndex", "description": "", @@ -988,9 +1053,22 @@ } }, "defaultValue": null - }, + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "indexFields", + "description": "The list of fields defined in the index mappings", + "args": [ { - "name": "docValueFields", + "name": "defaultIndex", "description": "", "type": { "kind": "NON_NULL", @@ -1001,11 +1079,7 @@ "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "docValueFieldsInput", - "ofType": null - } + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } }, @@ -1015,4215 +1089,16 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "LastEventTimeData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "Hosts", - "description": "Gets Hosts based on timerange and specified criteria, or all events in the timerange if no criteria is specified", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "pagination", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "sort", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "HostsSortField", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - }, - { - "name": "docValueFields", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "docValueFieldsInput", - "ofType": null - } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "HostsData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "HostOverview", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "hostName", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "HostItem", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "HostFirstLastSeen", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "hostName", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - }, - { - "name": "docValueFields", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "docValueFieldsInput", - "ofType": null - } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "FirstLastSeenHost", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "IpOverview", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "ip", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - }, - { - "name": "docValueFields", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "docValueFieldsInput", - "ofType": null - } - } - } - }, - "defaultValue": null - } - ], - "type": { "kind": "OBJECT", "name": "IpOverviewData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "Users", - "description": "", - "args": [ - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "ip", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "pagination", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "sort", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "UsersSortField", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "flowTarget", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "FlowTarget", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "UsersData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "KpiNetwork", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { "kind": "OBJECT", "name": "KpiNetworkData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "KpiHosts", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostsData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "KpiHostDetails", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostDetailsData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "MatrixHistogram", - "description": "", - "args": [ - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "stackByField", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "histogramType", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "HistogramType", "ofType": null } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "MatrixHistogramOverTimeData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "NetworkTopCountries", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "ip", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "flowTarget", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "FlowTargetSourceDest", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "pagination", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "sort", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "NetworkTopTablesSortField", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkTopCountriesData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "NetworkTopNFlow", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "ip", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "flowTarget", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "FlowTargetSourceDest", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "pagination", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "sort", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "NetworkTopTablesSortField", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkTopNFlowData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "NetworkDns", - "description": "", - "args": [ - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "isPtrIncluded", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "pagination", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "sort", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "NetworkDnsSortField", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "stackByField", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkDnsData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "NetworkDnsHistogram", - "description": "", - "args": [ - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "stackByField", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "docValueFields", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "docValueFieldsInput", - "ofType": null - } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkDsOverTimeData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "NetworkHttp", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "ip", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "pagination", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "sort", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "NetworkHttpSortField", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkHttpData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "OverviewNetwork", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { "kind": "OBJECT", "name": "OverviewNetworkData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "OverviewHost", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { "kind": "OBJECT", "name": "OverviewHostData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "Tls", - "description": "", - "args": [ - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "ip", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "pagination", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "sort", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TlsSortField", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "flowTarget", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "FlowTargetSourceDest", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "TlsData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "UncommonProcesses", - "description": "Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified", - "args": [ - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "pagination", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "UncommonProcessesData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "whoAmI", - "description": "Just a simple example to get the app name", - "args": [], - "type": { "kind": "OBJECT", "name": "SayMyName", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SourceConfiguration", - "description": "A set of configuration options for a security data source", - "fields": [ - { - "name": "fields", - "description": "The field mapping to use for this source", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "SourceFields", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SourceFields", - "description": "A mapping of semantic fields to their document counterparts", - "fields": [ - { - "name": "container", - "description": "The field to identify a container by", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "host", - "description": "The fields to identify a host by", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "message", - "description": "The fields that may contain the log event message. The first field found win.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pod", - "description": "The field to identify a pod by", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "tiebreaker", - "description": "The field to use as a tiebreaker for log events that have identical timestamps", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timestamp", - "description": "The field to use as a timestamp for metrics and logs", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SourceStatus", - "description": "The status of an infrastructure data source", - "fields": [ - { - "name": "indicesExist", - "description": "Whether the configured alias or wildcard pattern resolve to any auditbeat indices", - "args": [ - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "indexFields", - "description": "The list of fields defined in the index mappings", - "args": [ - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "IndexField", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Boolean", - "description": "The `Boolean` scalar type represents `true` or `false`.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "IndexField", - "description": "A descriptor of a field in an index", - "fields": [ - { - "name": "category", - "description": "Where the field belong", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "example", - "description": "Example of field's value", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "indexes", - "description": "whether the field's belong to an alias index", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "The name of the field", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": "The type of the field's values as recognized by Kibana", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "searchable", - "description": "Whether the field's values can be efficiently searched for", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "aggregatable", - "description": "Whether the field's values can be aggregated", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": "Description of the field", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "format", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "esTypes", - "description": "the elastic type as mapped in the index", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArrayNoNullable", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "subType", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToIFieldSubTypeNonNullable", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ToStringArrayNoNullable", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ToIFieldSubTypeNonNullable", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "TimerangeInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "interval", - "description": "The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan.", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "to", - "description": "The end of the timerange", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "from", - "description": "The beginning of the timerange", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "activePage", - "description": "The activePage parameter defines the page of results you want to fetch", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "cursorStart", - "description": "The cursorStart parameter defines the start of the results to be displayed", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "fakePossibleCount", - "description": "The fakePossibleCount parameter determines the total count in order to show 5 additional pages", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "querySize", - "description": "The querySize parameter is the number of items to be returned", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "docValueFieldsInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "field", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "format", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "AuthenticationsData", - "description": "", - "fields": [ - { - "name": "edges", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "AuthenticationsEdges", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "totalCount", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pageInfo", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "inspect", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "AuthenticationsEdges", - "description": "", - "fields": [ - { - "name": "node", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "AuthenticationItem", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "cursor", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "AuthenticationItem", - "description": "", - "fields": [ - { - "name": "_id", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "failures", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "successes", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "user", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "UserEcsFields", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lastSuccess", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "LastSourceHost", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lastFailure", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "LastSourceHost", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "UserEcsFields", - "description": "", - "fields": [ - { - "name": "domain", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "full_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "email", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hash", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "group", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ToStringArray", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "LastSourceHost", - "description": "", - "fields": [ - { - "name": "timestamp", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "source", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "SourceEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "host", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "HostEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Date", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SourceEcsFields", - "description": "", - "fields": [ - { - "name": "bytes", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ip", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "port", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "domain", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "geo", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "packets", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ToNumberArray", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "GeoEcsFields", - "description": "", - "fields": [ - { - "name": "city_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "continent_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "country_iso_code", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "country_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "location", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Location", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "region_iso_code", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "region_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Location", - "description": "", - "fields": [ - { - "name": "lon", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lat", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "HostEcsFields", - "description": "", - "fields": [ - { - "name": "architecture", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ip", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "mac", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "os", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "OsEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "OsEcsFields", - "description": "", - "fields": [ - { - "name": "platform", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "full", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "family", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "version", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "kernel", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "CursorType", - "description": "", - "fields": [ - { - "name": "value", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "tiebreaker", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "PageInfoPaginated", - "description": "", - "fields": [ - { - "name": "activePage", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "fakeTotalCount", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "showMorePagesIndicator", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Inspect", - "description": "", - "fields": [ - { - "name": "dsl", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "response", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "PaginationInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "limit", - "description": "The limit parameter allows you to configure the maximum amount of items to be returned", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "cursor", - "description": "The cursor parameter defines the next result you want to fetch", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "tiebreaker", - "description": "The tiebreaker parameter allow to be more precise to fetch the next item", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "SortField", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "sortFieldId", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "direction", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TimelineData", - "description": "", - "fields": [ - { - "name": "edges", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "TimelineEdges", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "totalCount", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pageInfo", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "inspect", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TimelineEdges", - "description": "", - "fields": [ - { - "name": "node", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "TimelineItem", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "cursor", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TimelineItem", - "description": "", - "fields": [ - { - "name": "_id", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "_index", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "data", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "TimelineNonEcsData", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ecs", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "ECS", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TimelineNonEcsData", - "description": "", - "fields": [ - { - "name": "field", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "value", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "ECS", - "description": "", - "fields": [ - { - "name": "_id", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "_index", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "agent", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "AgentEcsField", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "auditd", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "AuditdEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "destination", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "DestinationEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dns", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "DnsEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "endgame", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "EndgameEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "event", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "EventEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "geo", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "host", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "HostEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "network", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "NetworkEcsField", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "rule", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "RuleEcsField", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "signal", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "SignalField", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "source", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "SourceEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "suricata", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "SuricataEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "tls", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "TlsEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "zeek", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "ZeekEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "http", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "HttpEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "url", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "UrlEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timestamp", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "message", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "user", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "UserEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "winlog", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "WinlogEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "process", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "ProcessEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "file", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "FileFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "system", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "SystemEcsField", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "AgentEcsField", - "description": "", - "fields": [ - { - "name": "type", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "AuditdEcsFields", - "description": "", - "fields": [ - { - "name": "result", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "session", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "data", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "AuditdData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "summary", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Summary", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "sequence", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "AuditdData", - "description": "", - "fields": [ - { - "name": "acct", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "terminal", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "op", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Summary", - "description": "", - "fields": [ - { - "name": "actor", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "PrimarySecondary", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "object", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "PrimarySecondary", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "how", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "message_type", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "sequence", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "PrimarySecondary", - "description": "", - "fields": [ - { - "name": "primary", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "secondary", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "DestinationEcsFields", - "description": "", - "fields": [ - { - "name": "bytes", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ip", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "port", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "domain", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "geo", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "packets", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "DnsEcsFields", - "description": "", - "fields": [ - { - "name": "question", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "DnsQuestionData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "resolved_ip", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "response_code", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "DnsQuestionData", - "description": "", - "fields": [ - { - "name": "name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "EndgameEcsFields", - "description": "", - "fields": [ - { - "name": "exit_code", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "file_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "file_path", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "logon_type", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "parent_process_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pid", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "process_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "subject_domain_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "subject_logon_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "subject_user_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "target_domain_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "target_logon_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "target_user_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "EventEcsFields", - "description": "", - "fields": [ - { - "name": "action", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "category", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "code", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "created", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToDateArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dataset", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "duration", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "end", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToDateArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hash", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "kind", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "module", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "original", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "outcome", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "risk_score", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "risk_score_norm", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "severity", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "start", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToDateArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timezone", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ToDateArray", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkEcsField", - "description": "", - "fields": [ - { - "name": "bytes", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "community_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "direction", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "packets", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "protocol", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "transport", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "RuleEcsField", - "description": "", - "fields": [ - { - "name": "reference", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SignalField", - "description": "", - "fields": [ - { - "name": "rule", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "RuleField", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "original_time", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "status", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "RuleField", - "description": "", - "fields": [ - { - "name": "id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "rule_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "false_positives", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "saved_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timeline_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timeline_title", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "max_signals", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "risk_score", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "output_index", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "from", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "immutable", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "index", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "interval", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "language", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "query", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "references", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "severity", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "tags", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "threat", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "size", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "to", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "enabled", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "filters", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "created_at", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "updated_at", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "created_by", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "updated_by", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "version", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "note", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "threshold", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "exceptions_list", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ToBooleanArray", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ToAny", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SuricataEcsFields", - "description": "", - "fields": [ - { - "name": "eve", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "SuricataEveData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SuricataEveData", - "description": "", - "fields": [ - { - "name": "alert", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "SuricataAlertData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "flow_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "proto", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SuricataAlertData", - "description": "", - "fields": [ - { - "name": "signature", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "signature_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TlsEcsFields", - "description": "", - "fields": [ - { - "name": "client_certificate", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "TlsClientCertificateData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "fingerprints", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "TlsFingerprintsData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "server_certificate", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "TlsServerCertificateData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TlsClientCertificateData", - "description": "", - "fields": [ - { - "name": "fingerprint", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "FingerprintData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "FingerprintData", - "description": "", - "fields": [ - { - "name": "sha1", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TlsFingerprintsData", - "description": "", - "fields": [ - { - "name": "ja3", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "TlsJa3Data", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TlsJa3Data", - "description": "", - "fields": [ - { - "name": "hash", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, "isDeprecated": false, "deprecationReason": null } @@ -5234,350 +1109,246 @@ "possibleTypes": null }, { - "kind": "OBJECT", - "name": "TlsServerCertificateData", - "description": "", - "fields": [ - { - "name": "fingerprint", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "FingerprintData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "ZeekEcsFields", + "kind": "INPUT_OBJECT", + "name": "TimerangeInput", "description": "", - "fields": [ - { - "name": "session_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "connection", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "ZeekConnectionData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "notice", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "ZeekNoticeData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dns", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "ZeekDnsData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, + "fields": null, + "inputFields": [ { - "name": "http", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "ZeekHttpData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "name": "interval", + "description": "The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "defaultValue": null }, { - "name": "files", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "ZeekFileData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "name": "to", + "description": "The end of the timerange", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "defaultValue": null }, { - "name": "ssl", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "ZeekSslData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "name": "from", + "description": "The beginning of the timerange", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "ZeekConnectionData", + "kind": "INPUT_OBJECT", + "name": "PaginationInputPaginated", "description": "", - "fields": [ - { - "name": "local_resp", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, + "fields": null, + "inputFields": [ { - "name": "local_orig", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "name": "activePage", + "description": "The activePage parameter defines the page of results you want to fetch", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "defaultValue": null }, { - "name": "missed_bytes", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "name": "cursorStart", + "description": "The cursorStart parameter defines the start of the results to be displayed", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "defaultValue": null }, { - "name": "state", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "name": "fakePossibleCount", + "description": "The fakePossibleCount parameter determines the total count in order to show 5 additional pages", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "defaultValue": null }, { - "name": "history", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "name": "querySize", + "description": "The querySize parameter is the number of items to be returned", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "ZeekNoticeData", + "kind": "INPUT_OBJECT", + "name": "HostsSortField", "description": "", - "fields": [ - { - "name": "suppress_for", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "msg", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "note", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "sub", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dst", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, + "fields": null, + "inputFields": [ { - "name": "dropped", + "name": "field", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "HostsFields", "ofType": null } + }, + "defaultValue": null }, { - "name": "peer_descr", + "name": "direction", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } + }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "ZeekDnsData", + "kind": "ENUM", + "name": "HostsFields", "description": "", - "fields": [ - { - "name": "AA", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "qclass_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "RD", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "qtype_name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "rejected", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "qtype", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "query", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ { - "name": "trans_id", + "name": "hostName", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "qclass", + "name": "lastSeen", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "docValueFieldsInput", + "description": "", + "fields": null, + "inputFields": [ { - "name": "RA", + "name": "field", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "defaultValue": null }, { - "name": "TC", + "name": "format", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "ZeekHttpData", + "name": "HostsData", "description": "", "fields": [ { - "name": "resp_mime_types", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "trans_depth", + "name": "edges", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "HostsEdges", "ofType": null } + } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "status_msg", + "name": "totalCount", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "resp_fuids", + "name": "pageInfo", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "tags", + "name": "inspect", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -5589,150 +1360,221 @@ }, { "kind": "OBJECT", - "name": "ZeekFileData", + "name": "HostsEdges", "description": "", "fields": [ { - "name": "session_ids", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timedout", + "name": "node", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "HostItem", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "local_orig", + "name": "cursor", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "HostItem", + "description": "", + "fields": [ { - "name": "tx_host", + "name": "_id", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "source", + "name": "cloud", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "CloudFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "is_orig", + "name": "endpoint", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "EndpointFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "overflow_bytes", + "name": "host", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "HostEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "sha1", + "name": "inspect", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "duration", + "name": "lastSeen", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CloudFields", + "description": "", + "fields": [ { - "name": "depth", + "name": "instance", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "CloudInstance", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "analyzers", + "name": "machine", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "CloudMachine", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "mime_type", + "name": "provider", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "rx_host", + "name": "region", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CloudInstance", + "description": "", + "fields": [ { - "name": "total_bytes", + "name": "id", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CloudMachine", + "description": "", + "fields": [ { - "name": "fuid", + "name": "type", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "EndpointFields", + "description": "", + "fields": [ { - "name": "seen_bytes", + "name": "endpointPolicy", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "missing_bytes", + "name": "sensorVersion", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "md5", + "name": "policyStatus", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "ENUM", "name": "HostPolicyResponseActionStatus", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -5743,55 +1585,36 @@ "possibleTypes": null }, { - "kind": "OBJECT", - "name": "ZeekSslData", + "kind": "ENUM", + "name": "HostPolicyResponseActionStatus", "description": "", - "fields": [ - { - "name": "cipher", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ { - "name": "established", + "name": "success", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "resumed", + "name": "failure", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, - { - "name": "version", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, + { "name": "warning", "description": "", "isDeprecated": false, "deprecationReason": null } + ], "possibleTypes": null }, { "kind": "OBJECT", - "name": "HttpEcsFields", + "name": "HostEcsFields", "description": "", "fields": [ { - "name": "version", + "name": "architecture", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, @@ -5799,34 +1622,23 @@ "deprecationReason": null }, { - "name": "request", + "name": "id", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "HttpRequestData", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "response", + "name": "ip", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "HttpResponseData", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "HttpRequestData", - "description": "", - "fields": [ + }, { - "name": "method", + "name": "mac", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, @@ -5834,26 +1646,26 @@ "deprecationReason": null }, { - "name": "body", + "name": "name", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "HttpBodyData", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "referrer", + "name": "os", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "OsEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "bytes", + "name": "type", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -5864,74 +1676,38 @@ "possibleTypes": null }, { - "kind": "OBJECT", - "name": "HttpBodyData", + "kind": "SCALAR", + "name": "ToStringArray", "description": "", - "fields": [ - { - "name": "content", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "bytes", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], + "fields": null, "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "HttpResponseData", + "name": "OsEcsFields", "description": "", "fields": [ { - "name": "status_code", + "name": "platform", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "body", + "name": "name", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "HttpBodyData", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "bytes", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "UrlEcsFields", - "description": "", - "fields": [ - { - "name": "domain", + "name": "full", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, @@ -5939,7 +1715,7 @@ "deprecationReason": null }, { - "name": "original", + "name": "family", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, @@ -5947,7 +1723,7 @@ "deprecationReason": null }, { - "name": "username", + "name": "version", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, @@ -5955,7 +1731,7 @@ "deprecationReason": null }, { - "name": "password", + "name": "kernel", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, @@ -5970,14 +1746,46 @@ }, { "kind": "OBJECT", - "name": "WinlogEcsFields", + "name": "Inspect", "description": "", "fields": [ { - "name": "event_id", + "name": "dsl", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "response", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, "isDeprecated": false, "deprecationReason": null } @@ -5987,88 +1795,116 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "SCALAR", + "name": "Date", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", - "name": "ProcessEcsFields", + "name": "CursorType", "description": "", "fields": [ { - "name": "hash", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "ProcessHashData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pid", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", + "name": "value", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ppid", + "name": "tiebreaker", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PageInfoPaginated", + "description": "", + "fields": [ { - "name": "args", + "name": "activePage", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "entity_id", + "name": "fakeTotalCount", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "executable", + "name": "showMorePagesIndicator", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FirstLastSeenHost", + "description": "", + "fields": [ { - "name": "title", + "name": "inspect", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "thread", + "name": "firstSeen", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Thread", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "working_directory", + "name": "lastSeen", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -6080,285 +1916,322 @@ }, { "kind": "OBJECT", - "name": "ProcessHashData", + "name": "TimelineResult", "description": "", "fields": [ { - "name": "md5", + "name": "columns", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "ColumnHeaderResult", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "sha1", + "name": "created", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "sha256", + "name": "createdBy", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Thread", - "description": "", - "fields": [ + }, { - "name": "id", + "name": "dataProviders", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "DataProviderResult", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "start", + "name": "dateRange", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "DateRangePickerResult", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "FileFields", - "description": "", - "fields": [ + }, { - "name": "name", + "name": "description", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "path", + "name": "eventIdToNoteIds", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "NoteResult", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "target_path", + "name": "eventType", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "extension", + "name": "excludedRowRendererIds", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "RowRendererId", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "type", + "name": "favorite", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "FavoriteTimelineResult", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "device", + "name": "filters", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "FilterTimelineResult", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inode", + "name": "kqlMode", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "uid", + "name": "kqlQuery", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "OBJECT", "name": "SerializedFilterQueryResult", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "owner", + "name": "indexNames", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "gid", + "name": "notes", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "NoteResult", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "group", + "name": "noteIds", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "mode", + "name": "pinnedEventIds", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "size", + "name": "pinnedEventsSaveObject", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "PinnedEvent", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "mtime", + "name": "savedQueryId", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToDateArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ctime", + "name": "savedObjectId", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToDateArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SystemEcsField", - "description": "", - "fields": [ + }, { - "name": "audit", + "name": "sort", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "AuditEcsFields", "ofType": null }, + "type": { "kind": "OBJECT", "name": "SortTimelineResult", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "auth", + "name": "status", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "AuthEcsFields", "ofType": null }, + "type": { "kind": "ENUM", "name": "TimelineStatus", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "AuditEcsFields", - "description": "", - "fields": [ + }, { - "name": "package", + "name": "title", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "PackageEcsFields", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "PackageEcsFields", - "description": "", - "fields": [ + }, { - "name": "arch", + "name": "templateTimelineId", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "entity_id", + "name": "templateTimelineVersion", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Int", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "name", + "name": "timelineType", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "ENUM", "name": "TimelineType", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "size", + "name": "updated", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "summary", + "name": "updatedBy", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, @@ -6366,7 +2239,11 @@ "name": "version", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null } @@ -6378,84 +2255,51 @@ }, { "kind": "OBJECT", - "name": "AuthEcsFields", + "name": "ColumnHeaderResult", "description": "", "fields": [ { - "name": "ssh", + "name": "aggregatable", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "SshEcsFields", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SshEcsFields", - "description": "", - "fields": [ + }, { - "name": "method", + "name": "category", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "signature", + "name": "columnHeaderType", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "PageInfo", - "description": "", - "fields": [ + }, { - "name": "endCursor", + "name": "description", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "CursorType", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "hasNextPage", + "name": "example", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TimelineDetailsData", - "description": "", - "fields": [ + }, { - "name": "data", + "name": "indexes", "description": "", "args": [], "type": { @@ -6464,56 +2308,49 @@ "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "DetailItem", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", + "name": "id", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "DetailItem", - "description": "", - "fields": [ + }, { - "name": "field", + "name": "name", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "values", + "name": "placeholder", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "searchable", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "originalValue", + "name": "type", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "EsValue", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -6524,198 +2361,130 @@ "possibleTypes": null }, { - "kind": "SCALAR", - "name": "EsValue", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "LastEventIndexKey", + "kind": "OBJECT", + "name": "DataProviderResult", "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ + "fields": [ { - "name": "hostDetails", + "name": "id", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, - { "name": "hosts", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "ipDetails", + "name": "name", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, - { "name": "network", "description": "", "isDeprecated": false, "deprecationReason": null } - ], - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "LastTimeDetails", - "description": "", - "fields": null, - "inputFields": [ { - "name": "hostName", + "name": "enabled", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "ip", + "name": "excluded", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "LastEventTimeData", - "description": "", - "fields": [ + "args": [], + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, { - "name": "lastSeen", + "name": "kqlQuery", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", + "name": "queryMatch", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { "kind": "OBJECT", "name": "QueryMatchResult", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "HostsSortField", - "description": "", - "fields": null, - "inputFields": [ + }, { - "name": "field", + "name": "type", "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "HostsFields", "ofType": null } - }, - "defaultValue": null + "args": [], + "type": { "kind": "ENUM", "name": "DataProviderType", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "direction", + "name": "and", "description": "", + "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, - "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "DataProviderResult", "ofType": null } + } }, - "defaultValue": null + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { - "kind": "ENUM", - "name": "HostsFields", + "kind": "OBJECT", + "name": "QueryMatchResult", "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ + "fields": [ { - "name": "hostName", + "name": "field", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "lastSeen", - "description": "", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "HostsData", - "description": "", - "fields": [ - { - "name": "edges", + "name": "displayField", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "HostsEdges", "ofType": null } - } - } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "totalCount", + "name": "value", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "pageInfo", + "name": "displayValue", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", + "name": "operator", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -6725,32 +2494,47 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "ENUM", + "name": "DataProviderType", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "default", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "template", + "description": "", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, { "kind": "OBJECT", - "name": "HostsEdges", + "name": "DateRangePickerResult", "description": "", "fields": [ { - "name": "node", + "name": "start", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "HostItem", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "cursor", + "name": "end", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -6761,106 +2545,110 @@ "possibleTypes": null }, { - "kind": "OBJECT", - "name": "HostItem", + "kind": "SCALAR", + "name": "ToAny", "description": "", - "fields": [ + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "RowRendererId", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { "name": "auditd", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "_id", + "name": "auditd_file", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "cloud", + "name": "netflow", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "CloudFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, + { "name": "plain", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "endpoint", + "name": "suricata", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "EndpointFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, + { "name": "system", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "host", + "name": "system_dns", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "HostEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", + "name": "system_endgame_process", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "lastSeen", + "name": "system_file", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } + }, + { + "name": "system_fim", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "system_security_event", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "system_socket", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "zeek", "description": "", "isDeprecated": false, "deprecationReason": null } ], - "inputFields": null, - "interfaces": [], - "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "CloudFields", + "name": "FavoriteTimelineResult", "description": "", "fields": [ { - "name": "instance", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "CloudInstance", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "machine", + "name": "fullName", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "CloudMachine", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "provider", + "name": "userName", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "region", + "name": "favoriteDate", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -6872,57 +2660,27 @@ }, { "kind": "OBJECT", - "name": "CloudInstance", + "name": "FilterTimelineResult", "description": "", "fields": [ { - "name": "id", + "name": "exists", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "CloudMachine", - "description": "", - "fields": [ + }, { - "name": "type", + "name": "meta", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "FilterMetaTimelineResult", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "EndpointFields", - "description": "", - "fields": [ + }, { - "name": "endpointPolicy", + "name": "match_all", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -6930,7 +2688,7 @@ "deprecationReason": null }, { - "name": "sensorVersion", + "name": "missing", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -6938,10 +2696,26 @@ "deprecationReason": null }, { - "name": "policyStatus", + "name": "query", "description": "", "args": [], - "type": { "kind": "ENUM", "name": "HostPolicyResponseActionStatus", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "range", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "script", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -6952,118 +2726,95 @@ "possibleTypes": null }, { - "kind": "ENUM", - "name": "HostPolicyResponseActionStatus", + "kind": "OBJECT", + "name": "FilterMetaTimelineResult", "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ + "fields": [ { - "name": "success", + "name": "alias", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "failure", + "name": "controlledBy", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, - { "name": "warning", "description": "", "isDeprecated": false, "deprecationReason": null } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "FirstLastSeenHost", - "description": "", - "fields": [ { - "name": "inspect", + "name": "disabled", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "firstSeen", + "name": "field", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "lastSeen", + "name": "formattedValue", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "IpOverviewData", - "description": "", - "fields": [ + }, { - "name": "client", + "name": "index", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Overview", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "destination", + "name": "key", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Overview", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "host", + "name": "negate", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "HostEcsFields", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "server", + "name": "params", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Overview", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "source", + "name": "type", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Overview", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", + "name": "value", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -7075,46 +2826,41 @@ }, { "kind": "OBJECT", - "name": "Overview", + "name": "SerializedFilterQueryResult", "description": "", "fields": [ { - "name": "firstSeen", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lastSeen", + "name": "filterQuery", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, + "type": { "kind": "OBJECT", "name": "SerializedKueryQueryResult", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SerializedKueryQueryResult", + "description": "", + "fields": [ { - "name": "autonomousSystem", + "name": "kuery", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "AutonomousSystem", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "KueryFilterQueryResult", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "geo", + "name": "serializedQuery", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -7126,22 +2872,22 @@ }, { "kind": "OBJECT", - "name": "AutonomousSystem", + "name": "KueryFilterQueryResult", "description": "", "fields": [ { - "name": "number", + "name": "kind", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "organization", + "name": "expression", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "AutonomousSystemOrganization", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -7153,11 +2899,19 @@ }, { "kind": "OBJECT", - "name": "AutonomousSystemOrganization", + "name": "SortTimelineResult", "description": "", "fields": [ { - "name": "name", + "name": "columnId", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "sortDirection", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -7171,268 +2925,167 @@ "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "UsersSortField", + "kind": "ENUM", + "name": "TimelineStatus", "description": "", "fields": null, - "inputFields": [ - { - "name": "field", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "UsersFields", "ofType": null } - }, - "defaultValue": null - }, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { "name": "active", "description": "", "isDeprecated": false, "deprecationReason": null }, + { "name": "draft", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "direction", + "name": "immutable", "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } - }, - "defaultValue": null + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, - "enumValues": null, "possibleTypes": null }, { - "kind": "ENUM", - "name": "UsersFields", - "description": "", + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ", "fields": null, "inputFields": null, "interfaces": null, - "enumValues": [ - { "name": "name", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "count", "description": "", "isDeprecated": false, "deprecationReason": null } - ], + "enumValues": null, "possibleTypes": null }, { "kind": "ENUM", - "name": "FlowTarget", + "name": "TimelineType", "description": "", "fields": null, "inputFields": null, "interfaces": null, "enumValues": [ - { "name": "client", "description": "", "isDeprecated": false, "deprecationReason": null }, - { - "name": "destination", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { "name": "server", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "source", "description": "", "isDeprecated": false, "deprecationReason": null } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "UsersData", - "description": "", - "fields": [ - { - "name": "edges", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "UsersEdges", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "totalCount", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, { - "name": "pageInfo", + "name": "default", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } - }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", + "name": "template", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], - "inputFields": null, - "interfaces": [], - "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "UsersEdges", + "kind": "INPUT_OBJECT", + "name": "PageInfoTimeline", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "node", + "name": "pageIndex", "description": "", - "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "UsersNode", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } }, - "isDeprecated": false, - "deprecationReason": null + "defaultValue": null }, { - "name": "cursor", + "name": "pageSize", "description": "", - "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } }, - "isDeprecated": false, - "deprecationReason": null + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "UsersNode", + "kind": "INPUT_OBJECT", + "name": "SortTimeline", "description": "", - "fields": [ - { - "name": "_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, + "fields": null, + "inputFields": [ { - "name": "timestamp", + "name": "sortField", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "SortFieldTimeline", "ofType": null } + }, + "defaultValue": null }, { - "name": "user", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "UsersItem", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "name": "sortOrder", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } + }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "UsersItem", + "kind": "ENUM", + "name": "SortFieldTimeline", "description": "", - "fields": [ - { - "name": "name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { "name": "title", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "groupId", + "name": "description", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "groupName", + "name": "updated", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, - { - "name": "count", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } + { "name": "created", "description": "", "isDeprecated": false, "deprecationReason": null } ], - "inputFields": null, - "interfaces": [], - "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "KpiNetworkData", + "name": "ResponseTimelines", "description": "", "fields": [ { - "name": "networkEvents", + "name": "timeline", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { "kind": "OBJECT", "name": "TimelineResult", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "uniqueFlowId", + "name": "totalCount", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, @@ -7440,7 +3093,7 @@ "deprecationReason": null }, { - "name": "uniqueSourcePrivateIps", + "name": "defaultTimelineCount", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, @@ -7448,23 +3101,7 @@ "deprecationReason": null }, { - "name": "uniqueSourcePrivateIpsHistogram", - "description": "", - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiNetworkHistogramData", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "uniqueDestinationPrivateIps", + "name": "templateTimelineCount", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, @@ -7472,23 +3109,7 @@ "deprecationReason": null }, { - "name": "uniqueDestinationPrivateIpsHistogram", - "description": "", - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiNetworkHistogramData", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dnsQueries", + "name": "elasticTemplateTimelineCount", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, @@ -7496,7 +3117,7 @@ "deprecationReason": null }, { - "name": "tlsHandshakes", + "name": "customTemplateTimelineCount", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, @@ -7504,10 +3125,10 @@ "deprecationReason": null }, { - "name": "inspect", + "name": "favoriteCount", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -7519,177 +3140,308 @@ }, { "kind": "OBJECT", - "name": "KpiNetworkHistogramData", + "name": "Mutation", "description": "", "fields": [ { - "name": "x", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "persistNote", + "description": "Persists a note", + "args": [ + { + "name": "noteId", + "description": "", + "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, + "defaultValue": null + }, + { + "name": "version", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "note", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "INPUT_OBJECT", "name": "NoteInput", "ofType": null } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "ResponseNote", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "y", + "name": "deleteNote", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "args": [ + { + "name": "id", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + } + } + }, + "defaultValue": null + } + ], + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "KpiHostsData", - "description": "", - "fields": [ + }, { - "name": "hosts", + "name": "deleteNoteByTimelineId", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "args": [ + { + "name": "timelineId", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + }, + "defaultValue": null + }, + { + "name": "version", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + } + ], + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "hostsHistogram", - "description": "", - "args": [], + "name": "persistPinnedEventOnTimeline", + "description": "Persists a pinned event in a timeline", + "args": [ + { + "name": "pinnedEventId", + "description": "", + "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, + "defaultValue": null + }, + { + "name": "eventId", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + }, + "defaultValue": null + }, + { + "name": "timelineId", + "description": "", + "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, + "defaultValue": null + } + ], + "type": { "kind": "OBJECT", "name": "PinnedEvent", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deletePinnedEventOnTimeline", + "description": "Remove a pinned events in a timeline", + "args": [ + { + "name": "id", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + } + } + }, + "defaultValue": null + } + ], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostHistogramData", "ofType": null } - } + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "authSuccess", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "authSuccessHistogram", - "description": "", - "args": [], + "name": "deleteAllPinnedEventsOnTimeline", + "description": "Remove all pinned events in a timeline", + "args": [ + { + "name": "timelineId", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + }, + "defaultValue": null + } + ], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostHistogramData", "ofType": null } - } + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "authFailure", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "authFailureHistogram", - "description": "", - "args": [], + "name": "persistTimeline", + "description": "Persists a timeline", + "args": [ + { + "name": "id", + "description": "", + "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, + "defaultValue": null + }, + { + "name": "version", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "timeline", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "INPUT_OBJECT", "name": "TimelineInput", "ofType": null } + }, + "defaultValue": null + } + ], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostHistogramData", "ofType": null } - } + "ofType": { "kind": "OBJECT", "name": "ResponseTimeline", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "uniqueSourceIps", + "name": "persistFavorite", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "args": [ + { + "name": "timelineId", + "description": "", + "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "ResponseFavoriteTimeline", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "uniqueSourceIpsHistogram", + "name": "deleteTimeline", "description": "", - "args": [], + "args": [ + { + "name": "id", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + } + } + }, + "defaultValue": null + } + ], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostHistogramData", "ofType": null } - } + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "NoteInput", + "description": "", + "fields": null, + "inputFields": [ { - "name": "uniqueDestinationIps", + "name": "eventId", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "uniqueDestinationIpsHistogram", + "name": "note", "description": "", - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostHistogramData", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "inspect", + "name": "timelineId", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "KpiHostHistogramData", + "name": "ResponseNote", "description": "", "fields": [ { - "name": "x", + "name": "code", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, @@ -7697,10 +3449,22 @@ "deprecationReason": null }, { - "name": "y", + "name": "message", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "note", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "NoteResult", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null } @@ -7711,750 +3475,624 @@ "possibleTypes": null }, { - "kind": "OBJECT", - "name": "KpiHostDetailsData", + "kind": "INPUT_OBJECT", + "name": "TimelineInput", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "authSuccess", + "name": "columns", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "INPUT_OBJECT", "name": "ColumnHeaderInput", "ofType": null } + } + }, + "defaultValue": null }, { - "name": "authSuccessHistogram", + "name": "dataProviders", "description": "", - "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostHistogramData", "ofType": null } + "ofType": { "kind": "INPUT_OBJECT", "name": "DataProviderInput", "ofType": null } } }, - "isDeprecated": false, - "deprecationReason": null + "defaultValue": null }, { - "name": "authFailure", + "name": "description", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "eventType", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "authFailureHistogram", + "name": "excludedRowRendererIds", "description": "", - "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostHistogramData", "ofType": null } + "ofType": { "kind": "ENUM", "name": "RowRendererId", "ofType": null } } }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "uniqueSourceIps", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "defaultValue": null }, { - "name": "uniqueSourceIpsHistogram", + "name": "filters", "description": "", - "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostHistogramData", "ofType": null } + "ofType": { "kind": "INPUT_OBJECT", "name": "FilterTimelineInput", "ofType": null } } }, - "isDeprecated": false, - "deprecationReason": null + "defaultValue": null }, { - "name": "uniqueDestinationIps", + "name": "kqlMode", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "kqlQuery", + "description": "", + "type": { + "kind": "INPUT_OBJECT", + "name": "SerializedFilterQueryInput", + "ofType": null + }, + "defaultValue": null }, { - "name": "uniqueDestinationIpsHistogram", + "name": "indexNames", "description": "", - "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "KpiHostHistogramData", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } }, - "isDeprecated": false, - "deprecationReason": null + "defaultValue": null }, { - "name": "inspect", + "name": "title", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "HistogramType", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, { - "name": "authentications", + "name": "templateTimelineId", "description": "", - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "anomalies", + "name": "templateTimelineVersion", "description": "", - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "Int", "ofType": null }, + "defaultValue": null }, - { "name": "events", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "alerts", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "dns", "description": "", "isDeprecated": false, "deprecationReason": null } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "MatrixHistogramOverTimeData", - "description": "", - "fields": [ { - "name": "inspect", + "name": "timelineType", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "ENUM", "name": "TimelineType", "ofType": null }, + "defaultValue": null }, { - "name": "matrixHistogramData", + "name": "dateRange", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "MatrixOverTimeHistogramData", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "INPUT_OBJECT", "name": "DateRangePickerInput", "ofType": null }, + "defaultValue": null }, { - "name": "totalCount", + "name": "savedQueryId", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "sort", + "description": "", + "type": { "kind": "INPUT_OBJECT", "name": "SortTimelineInput", "ofType": null }, + "defaultValue": null + }, + { + "name": "status", + "description": "", + "type": { "kind": "ENUM", "name": "TimelineStatus", "ofType": null }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "MatrixOverTimeHistogramData", + "kind": "INPUT_OBJECT", + "name": "ColumnHeaderInput", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "x", + "name": "aggregatable", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": null }, { - "name": "y", + "name": "category", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "g", + "name": "columnHeaderType", "description": "", - "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "FlowTargetSourceDest", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ + "defaultValue": null + }, { - "name": "destination", + "name": "description", "description": "", - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, - { "name": "source", "description": "", "isDeprecated": false, "deprecationReason": null } - ], - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "NetworkTopTablesSortField", - "description": "", - "fields": null, - "inputFields": [ { - "name": "field", + "name": "example", "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "NetworkTopTablesFields", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "defaultValue": null }, { - "name": "direction", + "name": "indexes", "description": "", "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, - "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } }, "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "NetworkTopTablesFields", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ + }, { - "name": "bytes_in", + "name": "id", "description": "", - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "bytes_out", + "name": "name", "description": "", - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, - { "name": "flows", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "destination_ips", + "name": "placeholder", "description": "", - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "source_ips", + "name": "searchable", "description": "", - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": null + }, + { + "name": "type", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null } ], + "interfaces": null, + "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "NetworkTopCountriesData", + "kind": "INPUT_OBJECT", + "name": "DataProviderInput", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "edges", + "name": "id", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkTopCountriesEdges", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "totalCount", + "name": "name", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "enabled", + "description": "", + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": null + }, + { + "name": "excluded", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": null }, { - "name": "pageInfo", + "name": "kqlQuery", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "inspect", + "name": "queryMatch", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkTopCountriesEdges", - "description": "", - "fields": [ + "type": { "kind": "INPUT_OBJECT", "name": "QueryMatchInput", "ofType": null }, + "defaultValue": null + }, { - "name": "node", + "name": "and", "description": "", - "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkTopCountriesItem", "ofType": null } + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "INPUT_OBJECT", "name": "DataProviderInput", "ofType": null } + } }, - "isDeprecated": false, - "deprecationReason": null + "defaultValue": null }, { - "name": "cursor", + "name": "type", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "ENUM", "name": "DataProviderType", "ofType": null }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "NetworkTopCountriesItem", + "kind": "INPUT_OBJECT", + "name": "QueryMatchInput", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "_id", + "name": "field", "description": "", - "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "defaultValue": null }, { - "name": "source", + "name": "displayField", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "TopCountriesItemSource", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "destination", + "name": "value", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "TopCountriesItemDestination", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "network", + "name": "displayValue", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "TopNetworkTablesEcsField", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "operator", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "TopCountriesItemSource", + "kind": "INPUT_OBJECT", + "name": "FilterTimelineInput", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "country", + "name": "exists", "description": "", - "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "defaultValue": null }, { - "name": "destination_ips", + "name": "meta", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "INPUT_OBJECT", "name": "FilterMetaTimelineInput", "ofType": null }, + "defaultValue": null }, { - "name": "flows", + "name": "match_all", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "location", + "name": "missing", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "GeoItem", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "source_ips", + "name": "query", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "GeoItem", - "description": "", - "fields": [ + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, { - "name": "geo", + "name": "range", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "flowTarget", + "name": "script", "description": "", - "args": [], - "type": { "kind": "ENUM", "name": "FlowTargetSourceDest", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "TopCountriesItemDestination", + "kind": "INPUT_OBJECT", + "name": "FilterMetaTimelineInput", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "country", + "name": "alias", "description": "", - "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "defaultValue": null + }, + { + "name": "controlledBy", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "destination_ips", + "name": "disabled", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": null }, { - "name": "flows", + "name": "field", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "location", + "name": "formattedValue", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "GeoItem", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "index", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "source_ips", + "name": "key", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "negate", + "description": "", + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": null + }, + { + "name": "params", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "type", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "value", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "SerializedFilterQueryInput", + "description": "", + "fields": null, + "inputFields": [ + { + "name": "filterQuery", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "INPUT_OBJECT", "name": "SerializedKueryQueryInput", "ofType": null }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "TopNetworkTablesEcsField", + "kind": "INPUT_OBJECT", + "name": "SerializedKueryQueryInput", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "bytes_in", + "name": "kuery", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "INPUT_OBJECT", "name": "KueryFilterQueryInput", "ofType": null }, + "defaultValue": null }, { - "name": "bytes_out", + "name": "serializedQuery", "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "NetworkTopNFlowData", + "kind": "INPUT_OBJECT", + "name": "KueryFilterQueryInput", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "edges", + "name": "kind", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkTopNFlowEdges", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "totalCount", + "name": "expression", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "DateRangePickerInput", + "description": "", + "fields": null, + "inputFields": [ { - "name": "pageInfo", + "name": "start", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, + "defaultValue": null }, { - "name": "inspect", + "name": "end", "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "NetworkTopNFlowEdges", + "kind": "INPUT_OBJECT", + "name": "SortTimelineInput", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "node", + "name": "columnId", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkTopNFlowItem", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null }, { - "name": "cursor", + "name": "sortDirection", "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "NetworkTopNFlowItem", + "name": "ResponseTimeline", "description": "", "fields": [ { - "name": "_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "source", + "name": "code", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "TopNFlowItemSource", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "destination", + "name": "message", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "TopNFlowItemDestination", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "network", + "name": "timeline", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "TopNetworkTablesEcsField", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "TimelineResult", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null } @@ -8466,35 +4104,19 @@ }, { "kind": "OBJECT", - "name": "TopNFlowItemSource", + "name": "ResponseFavoriteTimeline", "description": "", "fields": [ { - "name": "autonomous_system", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "AutonomousSystemItem", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "domain", + "name": "code", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ip", + "name": "message", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -8502,53 +4124,42 @@ "deprecationReason": null }, { - "name": "location", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "GeoItem", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "flows", + "name": "savedObjectId", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "destination_ips", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "AutonomousSystemItem", - "description": "", - "fields": [ - { - "name": "name", + "name": "version", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "number", + "name": "favorite", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "FavoriteTimelineResult", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null } @@ -8560,62 +4171,74 @@ }, { "kind": "OBJECT", - "name": "TopNFlowItemDestination", - "description": "", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", "fields": [ { - "name": "autonomous_system", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "AutonomousSystemItem", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "domain", - "description": "", + "name": "types", + "description": "A list of all types supported by this server.", "args": [], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + } } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ip", - "description": "", + "name": "queryType", + "description": "The type that query operations will be rooted at.", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "location", - "description": "", + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", "args": [], - "type": { "kind": "OBJECT", "name": "GeoItem", "ofType": null }, + "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "flows", - "description": "", + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "source_ips", - "description": "", + "name": "directives", + "description": "A list of all directives supported by this server.", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Directive", "ofType": null } + } + } + }, "isDeprecated": false, "deprecationReason": null } @@ -8626,137 +4249,119 @@ "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "NetworkDnsSortField", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "field", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "NetworkDnsFields", "ofType": null } - }, - "defaultValue": null - }, + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ { - "name": "direction", - "description": "", + "name": "kind", + "description": null, + "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } + "ofType": { "kind": "ENUM", "name": "__TypeKind", "ofType": null } }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "NetworkDnsFields", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "dnsName", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "queryCount", - "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "uniqueDomains", - "description": "", + "name": "name", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dnsBytesIn", - "description": "", + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dnsBytesOut", - "description": "", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkDnsData", - "description": "", - "fields": [ - { - "name": "edges", - "description": "", - "args": [], + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": "false" + } + ], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkDnsEdges", "ofType": null } - } + "ofType": { "kind": "OBJECT", "name": "__Field", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "totalCount", - "description": "", + "name": "interfaces", + "description": null, "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "pageInfo", - "description": "", + "name": "possibleTypes", + "description": null, "args": [], "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__EnumValue", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "histogram", - "description": "", + "name": "inputFields", + "description": null, "args": [], "type": { "kind": "LIST", @@ -8764,15 +4369,19 @@ "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "OBJECT", - "name": "MatrixOverOrdinalHistogramData", - "ofType": null - } + "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -8781,90 +4390,138 @@ "possibleTypes": null }, { - "kind": "OBJECT", - "name": "NetworkDnsEdges", - "description": "", - "fields": [ + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ { - "name": "node", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkDnsItem", "ofType": null } - }, + "name": "SCALAR", + "description": "Indicates this type is a scalar.", "isDeprecated": false, "deprecationReason": null }, { - "name": "cursor", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", "isDeprecated": false, "deprecationReason": null } ], - "inputFields": null, - "interfaces": [], - "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "NetworkDnsItem", - "description": "", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", "fields": [ { - "name": "_id", - "description": "", + "name": "name", + "description": null, "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dnsBytesIn", - "description": "", + "name": "description", + "description": null, "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dnsBytesOut", - "description": "", + "name": "args", + "description": null, "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } + } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dnsName", - "description": "", + "name": "type", + "description": null, "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "queryCount", - "description": "", + "name": "isDeprecated", + "description": null, "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "uniqueDomains", - "description": "", + "name": "deprecationReason", + "description": null, "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -8876,12 +4533,12 @@ }, { "kind": "OBJECT", - "name": "MatrixOverOrdinalHistogramData", - "description": "", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", "fields": [ { - "name": "x", - "description": "", + "name": "name", + "description": null, "args": [], "type": { "kind": "NON_NULL", @@ -8892,28 +4549,32 @@ "deprecationReason": null }, { - "name": "y", - "description": "", + "name": "description", + "description": null, "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "g", - "description": "", + "name": "type", + "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "defaultValue", + "description": "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -8923,52 +4584,48 @@ }, { "kind": "OBJECT", - "name": "NetworkDsOverTimeData", - "description": "", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", "fields": [ { - "name": "inspect", - "description": "", + "name": "name", + "description": null, "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "matrixHistogramData", - "description": "", + "name": "description", + "description": null, "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "MatrixOverTimeHistogramData", - "ofType": null - } - } - } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "totalCount", - "description": "", + "name": "isDeprecated", + "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -8977,34 +4634,33 @@ "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "NetworkHttpSortField", - "description": "", - "fields": null, - "inputFields": [ + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ { - "name": "direction", - "description": "", + "name": "name", + "description": null, + "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkHttpData", - "description": "", - "fields": [ + "isDeprecated": false, + "deprecationReason": null + }, { - "name": "edges", - "description": "", + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, "args": [], "type": { "kind": "NON_NULL", @@ -9015,7 +4671,7 @@ "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkHttpEdges", "ofType": null } + "ofType": { "kind": "ENUM", "name": "__DirectiveLocation", "ofType": null } } } }, @@ -9023,71 +4679,60 @@ "deprecationReason": null }, { - "name": "totalCount", - "description": "", + "name": "args", + "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } + } + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "pageInfo", - "description": "", + "name": "onOperation", + "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, - "isDeprecated": false, - "deprecationReason": null + "isDeprecated": true, + "deprecationReason": "Use `locations`." }, { - "name": "inspect", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkHttpEdges", - "description": "", - "fields": [ - { - "name": "node", - "description": "", + "name": "onFragment", + "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkHttpItem", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, - "isDeprecated": false, - "deprecationReason": null + "isDeprecated": true, + "deprecationReason": "Use `locations`." }, { - "name": "cursor", - "description": "", + "name": "onField", + "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } }, - "isDeprecated": false, - "deprecationReason": null + "isDeprecated": true, + "deprecationReason": "Use `locations`." } ], "inputFields": null, @@ -9096,451 +4741,288 @@ "possibleTypes": null }, { - "kind": "OBJECT", - "name": "NetworkHttpItem", - "description": "", - "fields": [ + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ { - "name": "_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "name": "QUERY", + "description": "Location adjacent to a query operation.", "isDeprecated": false, "deprecationReason": null }, { - "name": "domains", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", "isDeprecated": false, "deprecationReason": null }, { - "name": "lastHost", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", "isDeprecated": false, "deprecationReason": null }, { - "name": "lastSourceIp", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "name": "FIELD", + "description": "Location adjacent to a field.", "isDeprecated": false, "deprecationReason": null }, { - "name": "methods", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", "isDeprecated": false, "deprecationReason": null }, { - "name": "path", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", "isDeprecated": false, "deprecationReason": null }, { - "name": "requestCount", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", "isDeprecated": false, "deprecationReason": null }, { - "name": "statuses", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "OverviewNetworkData", - "description": "", - "fields": [ + }, { - "name": "auditbeatSocket", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", "isDeprecated": false, "deprecationReason": null }, { - "name": "filebeatCisco", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", "isDeprecated": false, "deprecationReason": null }, { - "name": "filebeatNetflow", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", "isDeprecated": false, "deprecationReason": null }, { - "name": "filebeatPanw", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition.", "isDeprecated": false, "deprecationReason": null }, { - "name": "filebeatSuricata", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", "isDeprecated": false, "deprecationReason": null }, { - "name": "filebeatZeek", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "UNION", + "description": "Location adjacent to a union definition.", "isDeprecated": false, "deprecationReason": null }, { - "name": "packetbeatDNS", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "ENUM", + "description": "Location adjacent to an enum definition.", "isDeprecated": false, "deprecationReason": null }, { - "name": "packetbeatFlow", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", "isDeprecated": false, "deprecationReason": null }, { - "name": "packetbeatTLS", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", "isDeprecated": false, "deprecationReason": null } ], + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ToStringArrayNoNullable", + "description": "", + "fields": null, "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "OverviewHostData", + "name": "EventEcsFields", "description": "", "fields": [ { - "name": "auditbeatAuditd", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "auditbeatFIM", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "auditbeatLogin", + "name": "action", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "auditbeatPackage", + "name": "category", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "auditbeatProcess", + "name": "code", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "auditbeatUser", + "name": "created", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToDateArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "endgameDns", + "name": "dataset", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "endgameFile", + "name": "duration", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "endgameImageLoad", + "name": "end", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToDateArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "endgameNetwork", + "name": "hash", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "endgameProcess", + "name": "id", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "endgameRegistry", + "name": "kind", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "endgameSecurity", + "name": "module", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "filebeatSystemModule", + "name": "original", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "winlogbeatSecurity", + "name": "outcome", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "winlogbeatMWSysmonOperational", + "name": "risk_score", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", + "name": "risk_score_norm", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "TlsSortField", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "field", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "TlsFields", "ofType": null } - }, - "defaultValue": null }, { - "name": "direction", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "TlsFields", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { "name": "_id", "description": "", "isDeprecated": false, "deprecationReason": null } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TlsData", - "description": "", - "fields": [ - { - "name": "edges", + "name": "severity", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "TlsEdges", "ofType": null } - } - } - }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "totalCount", + "name": "start", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToDateArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "pageInfo", + "name": "timezone", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", + "name": "type", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -9550,32 +5032,44 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "SCALAR", + "name": "ToDateArray", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ToNumberArray", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", - "name": "TlsEdges", + "name": "Location", "description": "", "fields": [ { - "name": "node", + "name": "lon", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "TlsNode", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "cursor", + "name": "lat", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -9587,149 +5081,62 @@ }, { "kind": "OBJECT", - "name": "TlsNode", + "name": "GeoEcsFields", "description": "", "fields": [ { - "name": "_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timestamp", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "notAfter", + "name": "city_name", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "subjects", + "name": "continent_name", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ja3", + "name": "country_iso_code", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "issuers", - "description": "", - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "UncommonProcessesData", - "description": "", - "fields": [ - { - "name": "edges", + "name": "country_name", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "UncommonProcessesEdges", "ofType": null } - } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "totalCount", + "name": "location", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "Location", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "pageInfo", + "name": "region_iso_code", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inspect", + "name": "region_name", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -9741,30 +5148,30 @@ }, { "kind": "OBJECT", - "name": "UncommonProcessesEdges", + "name": "PrimarySecondary", "description": "", "fields": [ { - "name": "node", + "name": "primary", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "UncommonProcessItem", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "cursor", + "name": "secondary", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -9776,70 +5183,46 @@ }, { "kind": "OBJECT", - "name": "UncommonProcessItem", + "name": "Summary", "description": "", "fields": [ { - "name": "_id", + "name": "actor", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "PrimarySecondary", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "instances", + "name": "object", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "PrimarySecondary", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "process", + "name": "how", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "ProcessEcsFields", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "hosts", + "name": "message_type", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "HostEcsFields", "ofType": null } - } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "user", + "name": "sequence", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "UserEcsFields", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -9851,18 +5234,14 @@ }, { "kind": "OBJECT", - "name": "SayMyName", + "name": "AgentEcsField", "description": "", "fields": [ { - "name": "appName", - "description": "The id of the source", + "name": "type", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -9874,318 +5253,293 @@ }, { "kind": "OBJECT", - "name": "TimelineResult", + "name": "AuditdData", "description": "", "fields": [ { - "name": "columns", + "name": "acct", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "ColumnHeaderResult", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "created", + "name": "terminal", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "createdBy", + "name": "op", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "AuditdEcsFields", + "description": "", + "fields": [ { - "name": "dataProviders", + "name": "result", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "DataProviderResult", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dateRange", + "name": "session", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "DateRangePickerResult", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "description", + "name": "data", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "AuditdData", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "eventIdToNoteIds", + "name": "summary", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NoteResult", "ofType": null } - } - }, + "type": { "kind": "OBJECT", "name": "Summary", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "eventType", + "name": "sequence", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Thread", + "description": "", + "fields": [ { - "name": "excludedRowRendererIds", + "name": "id", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "RowRendererId", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "favorite", + "name": "start", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "FavoriteTimelineResult", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ProcessHashData", + "description": "", + "fields": [ { - "name": "filters", + "name": "md5", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "FilterTimelineResult", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "kqlMode", + "name": "sha1", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "kqlQuery", + "name": "sha256", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "SerializedFilterQueryResult", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ProcessEcsFields", + "description": "", + "fields": [ { - "name": "notes", + "name": "hash", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NoteResult", "ofType": null } - } - }, + "type": { "kind": "OBJECT", "name": "ProcessHashData", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "noteIds", + "name": "pid", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "pinnedEventIds", + "name": "name", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "pinnedEventsSaveObject", + "name": "ppid", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PinnedEvent", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "savedQueryId", + "name": "args", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "savedObjectId", + "name": "entity_id", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "sort", + "name": "executable", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "SortTimelineResult", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "status", + "name": "title", "description": "", "args": [], - "type": { "kind": "ENUM", "name": "TimelineStatus", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "title", + "name": "thread", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "Thread", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "templateTimelineId", + "name": "working_directory", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SourceEcsFields", + "description": "", + "fields": [ + { + "name": "bytes", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "templateTimelineVersion", + "name": "ip", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Int", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "timelineType", + "name": "port", "description": "", "args": [], - "type": { "kind": "ENUM", "name": "TimelineType", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "updated", + "name": "domain", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "updatedBy", + "name": "geo", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "version", + "name": "packets", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -10197,102 +5551,116 @@ }, { "kind": "OBJECT", - "name": "ColumnHeaderResult", + "name": "DestinationEcsFields", "description": "", "fields": [ { - "name": "aggregatable", + "name": "bytes", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "category", + "name": "ip", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "columnHeaderType", + "name": "port", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "description", + "name": "domain", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "example", + "name": "geo", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "indexes", + "name": "packets", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "DnsQuestionData", + "description": "", + "fields": [ { - "name": "id", + "name": "name", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "name", + "name": "type", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "DnsEcsFields", + "description": "", + "fields": [ { - "name": "placeholder", + "name": "question", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "DnsQuestionData", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "searchable", + "name": "resolved_ip", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "type", + "name": "response_code", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -10304,129 +5672,110 @@ }, { "kind": "OBJECT", - "name": "DataProviderResult", + "name": "EndgameEcsFields", "description": "", "fields": [ { - "name": "id", + "name": "exit_code", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "name", + "name": "file_name", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "enabled", + "name": "file_path", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "excluded", + "name": "logon_type", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "kqlQuery", + "name": "parent_process_name", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "queryMatch", + "name": "pid", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "QueryMatchResult", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "type", + "name": "process_name", "description": "", "args": [], - "type": { "kind": "ENUM", "name": "DataProviderType", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "and", + "name": "subject_domain_name", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "DataProviderResult", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "QueryMatchResult", - "description": "", - "fields": [ + }, { - "name": "field", + "name": "subject_logon_id", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "displayField", + "name": "subject_user_name", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "value", + "name": "target_domain_name", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "displayValue", + "name": "target_logon_id", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "operator", + "name": "target_user_name", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -10436,151 +5785,97 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "ENUM", - "name": "DataProviderType", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "default", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "template", - "description": "", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - }, { "kind": "OBJECT", - "name": "DateRangePickerResult", + "name": "SuricataAlertData", "description": "", "fields": [ { - "name": "start", + "name": "signature", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "end", + "name": "signature_id", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "RowRendererId", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { "name": "auditd", "description": "", "isDeprecated": false, "deprecationReason": null }, - { - "name": "auditd_file", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "netflow", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { "name": "plain", "description": "", "isDeprecated": false, "deprecationReason": null }, - { - "name": "suricata", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { "name": "system", "description": "", "isDeprecated": false, "deprecationReason": null }, - { - "name": "system_dns", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "system_endgame_process", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "system_file", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SuricataEveData", + "description": "", + "fields": [ { - "name": "system_fim", + "name": "alert", "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "SuricataAlertData", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "system_security_event", + "name": "flow_id", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "system_socket", + "name": "proto", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, - { "name": "zeek", "description": "", "isDeprecated": false, "deprecationReason": null } + } ], + "inputFields": null, + "interfaces": [], + "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "FavoriteTimelineResult", + "name": "SuricataEcsFields", "description": "", "fields": [ { - "name": "fullName", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "userName", + "name": "eve", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "SuricataEveData", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TlsJa3Data", + "description": "", + "fields": [ { - "name": "favoriteDate", + "name": "hash", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -10592,62 +5887,106 @@ }, { "kind": "OBJECT", - "name": "FilterTimelineResult", + "name": "FingerprintData", "description": "", "fields": [ { - "name": "exists", + "name": "sha1", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TlsClientCertificateData", + "description": "", + "fields": [ { - "name": "meta", + "name": "fingerprint", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "FilterMetaTimelineResult", "ofType": null }, + "type": { "kind": "OBJECT", "name": "FingerprintData", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TlsServerCertificateData", + "description": "", + "fields": [ { - "name": "match_all", + "name": "fingerprint", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "FingerprintData", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TlsFingerprintsData", + "description": "", + "fields": [ { - "name": "missing", + "name": "ja3", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "TlsJa3Data", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TlsEcsFields", + "description": "", + "fields": [ { - "name": "query", + "name": "client_certificate", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "TlsClientCertificateData", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "range", + "name": "fingerprints", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "TlsFingerprintsData", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "script", + "name": "server_certificate", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "TlsServerCertificateData", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -10659,94 +5998,123 @@ }, { "kind": "OBJECT", - "name": "FilterMetaTimelineResult", + "name": "ZeekConnectionData", "description": "", "fields": [ { - "name": "alias", + "name": "local_resp", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "controlledBy", + "name": "local_orig", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "disabled", + "name": "missed_bytes", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "field", + "name": "state", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "formattedValue", + "name": "history", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ToBooleanArray", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ZeekNoticeData", + "description": "", + "fields": [ + { + "name": "suppress_for", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "index", + "name": "msg", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "key", + "name": "note", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "negate", + "name": "sub", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "params", + "name": "dst", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "type", + "name": "dropped", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "value", + "name": "peer_descr", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -10758,577 +6126,303 @@ }, { "kind": "OBJECT", - "name": "SerializedFilterQueryResult", + "name": "ZeekDnsData", "description": "", "fields": [ { - "name": "filterQuery", + "name": "AA", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "SerializedKueryQueryResult", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SerializedKueryQueryResult", - "description": "", - "fields": [ + }, { - "name": "kuery", + "name": "qclass_name", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "KueryFilterQueryResult", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "serializedQuery", + "name": "RD", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "KueryFilterQueryResult", - "description": "", - "fields": [ + }, { - "name": "kind", + "name": "qtype_name", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "expression", + "name": "rejected", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "SortTimelineResult", - "description": "", - "fields": [ + }, { - "name": "columnId", + "name": "qtype", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "sortDirection", + "name": "query", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "TimelineStatus", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { "name": "active", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "draft", "description": "", "isDeprecated": false, "deprecationReason": null }, + }, { - "name": "immutable", + "name": "trans_id", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Int", - "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "TimelineType", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ + }, { - "name": "default", + "name": "qclass", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "template", + "name": "RA", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "PageInfoTimeline", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "pageIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null }, { - "name": "pageSize", + "name": "TC", "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "SortTimeline", + "kind": "OBJECT", + "name": "FileFields", "description": "", - "fields": null, - "inputFields": [ + "fields": [ { - "name": "sortField", + "name": "name", "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "SortFieldTimeline", "ofType": null } - }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "sortOrder", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "SortFieldTimeline", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { "name": "title", "description": "", "isDeprecated": false, "deprecationReason": null }, - { - "name": "description", + "name": "path", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "updated", + "name": "target_path", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, - { "name": "created", "description": "", "isDeprecated": false, "deprecationReason": null } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "ResponseTimelines", - "description": "", - "fields": [ { - "name": "timeline", + "name": "extension", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { "kind": "OBJECT", "name": "TimelineResult", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "totalCount", + "name": "type", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "defaultTimelineCount", + "name": "device", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "templateTimelineCount", + "name": "inode", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "elasticTemplateTimelineCount", + "name": "uid", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "customTemplateTimelineCount", + "name": "owner", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "favoriteCount", + "name": "gid", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Mutation", - "description": "", - "fields": [ + }, { - "name": "persistNote", - "description": "Persists a note", - "args": [ - { - "name": "noteId", - "description": "", - "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, - "defaultValue": null - }, - { - "name": "version", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "note", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "NoteInput", "ofType": null } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "ResponseNote", "ofType": null } - }, + "name": "group", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "deleteNote", + "name": "mode", "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "deleteNoteByTimelineId", + "name": "size", "description": "", - "args": [ - { - "name": "timelineId", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "version", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "persistPinnedEventOnTimeline", - "description": "Persists a pinned event in a timeline", - "args": [ - { - "name": "pinnedEventId", - "description": "", - "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, - "defaultValue": null - }, - { - "name": "eventId", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "timelineId", - "description": "", - "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, - "defaultValue": null - } - ], - "type": { "kind": "OBJECT", "name": "PinnedEvent", "ofType": null }, + "name": "mtime", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToDateArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "deletePinnedEventOnTimeline", - "description": "Remove a pinned events in a timeline", - "args": [ - { - "name": "id", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, + "name": "ctime", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToDateArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ZeekHttpData", + "description": "", + "fields": [ + { + "name": "resp_mime_types", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "deleteAllPinnedEventsOnTimeline", - "description": "Remove all pinned events in a timeline", - "args": [ - { - "name": "timelineId", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, + "name": "trans_depth", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "persistTimeline", - "description": "Persists a timeline", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, - "defaultValue": null - }, - { - "name": "version", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timeline", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimelineInput", "ofType": null } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "ResponseTimeline", "ofType": null } - }, + "name": "status_msg", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "persistFavorite", + "name": "resp_fuids", "description": "", - "args": [ - { - "name": "timelineId", - "description": "", - "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "ResponseFavoriteTimeline", "ofType": null } - }, + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "deleteTimeline", + "name": "tags", "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "HttpBodyData", + "description": "", + "fields": [ + { + "name": "content", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "bytes", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -11339,64 +6433,74 @@ "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "NoteInput", + "kind": "OBJECT", + "name": "HttpRequestData", "description": "", - "fields": null, - "inputFields": [ + "fields": [ { - "name": "eventId", + "name": "method", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "note", + "name": "body", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "OBJECT", "name": "HttpBodyData", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "timelineId", + "name": "referrer", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "bytes", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "ResponseNote", + "name": "HttpResponseData", "description": "", "fields": [ { - "name": "code", + "name": "status_code", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "message", + "name": "body", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "HttpBodyData", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "note", + "name": "bytes", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NoteResult", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -11407,610 +6511,608 @@ "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "TimelineInput", + "kind": "OBJECT", + "name": "HttpEcsFields", "description": "", - "fields": null, - "inputFields": [ - { - "name": "columns", - "description": "", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "ColumnHeaderInput", "ofType": null } - } - }, - "defaultValue": null - }, - { - "name": "dataProviders", - "description": "", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "DataProviderInput", "ofType": null } - } - }, - "defaultValue": null - }, - { - "name": "description", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "eventType", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "excludedRowRendererIds", - "description": "", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "RowRendererId", "ofType": null } - } - }, - "defaultValue": null - }, - { - "name": "filters", - "description": "", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "FilterTimelineInput", "ofType": null } - } - }, - "defaultValue": null - }, - { - "name": "kqlMode", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "kqlQuery", - "description": "", - "type": { - "kind": "INPUT_OBJECT", - "name": "SerializedFilterQueryInput", - "ofType": null - }, - "defaultValue": null - }, - { - "name": "title", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, + "fields": [ { - "name": "templateTimelineId", + "name": "version", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "templateTimelineVersion", + "name": "request", "description": "", - "type": { "kind": "SCALAR", "name": "Int", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "OBJECT", "name": "HttpRequestData", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "timelineType", + "name": "response", "description": "", - "type": { "kind": "ENUM", "name": "TimelineType", "ofType": null }, - "defaultValue": null - }, + "args": [], + "type": { "kind": "OBJECT", "name": "HttpResponseData", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "UrlEcsFields", + "description": "", + "fields": [ { - "name": "dateRange", + "name": "domain", "description": "", - "type": { "kind": "INPUT_OBJECT", "name": "DateRangePickerInput", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "savedQueryId", + "name": "original", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "sort", + "name": "username", "description": "", - "type": { "kind": "INPUT_OBJECT", "name": "SortTimelineInput", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "status", + "name": "password", "description": "", - "type": { "kind": "ENUM", "name": "TimelineStatus", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "ColumnHeaderInput", + "kind": "OBJECT", + "name": "ZeekFileData", "description": "", - "fields": null, - "inputFields": [ - { - "name": "aggregatable", - "description": "", - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "defaultValue": null - }, + "fields": [ { - "name": "category", + "name": "session_ids", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "columnHeaderType", + "name": "timedout", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "description", + "name": "local_orig", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "example", + "name": "tx_host", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "indexes", + "name": "source", "description": "", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "id", + "name": "is_orig", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "name", + "name": "overflow_bytes", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "placeholder", + "name": "sha1", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "searchable", + "name": "duration", "description": "", - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "type", + "name": "depth", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "DataProviderInput", - "description": "", - "fields": null, - "inputFields": [ + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, { - "name": "id", + "name": "analyzers", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "name", + "name": "mime_type", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "enabled", + "name": "rx_host", "description": "", - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "excluded", + "name": "total_bytes", "description": "", - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "kqlQuery", + "name": "fuid", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "queryMatch", + "name": "seen_bytes", "description": "", - "type": { "kind": "INPUT_OBJECT", "name": "QueryMatchInput", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "and", + "name": "missing_bytes", "description": "", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "DataProviderInput", "ofType": null } - } - }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "type", + "name": "md5", "description": "", - "type": { "kind": "ENUM", "name": "DataProviderType", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "QueryMatchInput", + "kind": "OBJECT", + "name": "ZeekSslData", "description": "", - "fields": null, - "inputFields": [ - { - "name": "field", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, + "fields": [ { - "name": "displayField", + "name": "cipher", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "value", + "name": "established", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "displayValue", + "name": "resumed", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "operator", + "name": "version", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "FilterTimelineInput", + "kind": "OBJECT", + "name": "ZeekEcsFields", "description": "", - "fields": null, - "inputFields": [ + "fields": [ { - "name": "exists", + "name": "session_id", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "meta", + "name": "connection", "description": "", - "type": { "kind": "INPUT_OBJECT", "name": "FilterMetaTimelineInput", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "OBJECT", "name": "ZeekConnectionData", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "match_all", + "name": "notice", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "OBJECT", "name": "ZeekNoticeData", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "missing", + "name": "dns", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "OBJECT", "name": "ZeekDnsData", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "query", + "name": "http", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "OBJECT", "name": "ZeekHttpData", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "range", + "name": "files", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "OBJECT", "name": "ZeekFileData", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "script", + "name": "ssl", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "OBJECT", "name": "ZeekSslData", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "FilterMetaTimelineInput", + "kind": "OBJECT", + "name": "UserEcsFields", "description": "", - "fields": null, - "inputFields": [ - { - "name": "alias", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "controlledBy", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "disabled", - "description": "", - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "defaultValue": null - }, - { - "name": "field", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, + "fields": [ { - "name": "formattedValue", + "name": "domain", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "index", + "name": "id", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "key", + "name": "name", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "negate", + "name": "full_name", "description": "", - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "params", + "name": "email", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "type", + "name": "hash", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "value", + "name": "group", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "SerializedFilterQueryInput", + "kind": "OBJECT", + "name": "WinlogEcsFields", "description": "", - "fields": null, - "inputFields": [ + "fields": [ { - "name": "filterQuery", + "name": "event_id", "description": "", - "type": { "kind": "INPUT_OBJECT", "name": "SerializedKueryQueryInput", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "SerializedKueryQueryInput", + "kind": "OBJECT", + "name": "NetworkEcsField", "description": "", - "fields": null, - "inputFields": [ + "fields": [ { - "name": "kuery", + "name": "bytes", "description": "", - "type": { "kind": "INPUT_OBJECT", "name": "KueryFilterQueryInput", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "serializedQuery", + "name": "community_id", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "KueryFilterQueryInput", - "description": "", - "fields": null, - "inputFields": [ + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, { - "name": "kind", + "name": "direction", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "expression", + "name": "packets", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "protocol", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "transport", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "DateRangePickerInput", + "kind": "OBJECT", + "name": "PackageEcsFields", "description": "", - "fields": null, - "inputFields": [ + "fields": [ { - "name": "start", + "name": "arch", "description": "", - "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "end", + "name": "entity_id", "description": "", - "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "size", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "summary", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "version", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { - "kind": "INPUT_OBJECT", - "name": "SortTimelineInput", + "kind": "OBJECT", + "name": "AuditEcsFields", "description": "", - "fields": null, - "inputFields": [ - { - "name": "columnId", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, + "fields": [ { - "name": "sortDirection", + "name": "package", "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null + "args": [], + "type": { "kind": "OBJECT", "name": "PackageEcsFields", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], - "interfaces": null, + "inputFields": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "ResponseTimeline", + "name": "SshEcsFields", "description": "", "fields": [ { - "name": "code", + "name": "method", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "message", + "name": "signature", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "AuthEcsFields", + "description": "", + "fields": [ { - "name": "timeline", + "name": "ssh", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "TimelineResult", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "SshEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -12022,80 +7124,56 @@ }, { "kind": "OBJECT", - "name": "ResponseFavoriteTimeline", + "name": "SystemEcsField", "description": "", "fields": [ { - "name": "code", + "name": "audit", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "OBJECT", "name": "AuditEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "message", + "name": "auth", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "AuthEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "RuleField", + "description": "", + "fields": [ { - "name": "savedObjectId", + "name": "id", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "version", + "name": "rule_id", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "favorite", + "name": "false_positives", "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "FavoriteTimelineResult", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Schema", - "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", - "fields": [ - { - "name": "types", - "description": "A list of all types supported by this server.", - "args": [], "type": { "kind": "NON_NULL", "name": null, @@ -12105,7 +7183,7 @@ "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } }, @@ -12113,384 +7191,242 @@ "deprecationReason": null }, { - "name": "queryType", - "description": "The type that query operations will be rooted at.", + "name": "saved_id", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "mutationType", - "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "name": "timeline_id", + "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "subscriptionType", - "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "name": "timeline_title", + "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "directives", - "description": "A list of all directives supported by this server.", + "name": "max_signals", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Directive", "ofType": null } - } - } - }, + "type": { "kind": "SCALAR", "name": "ToNumberArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Type", - "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", - "fields": [ + }, { - "name": "kind", - "description": null, + "name": "risk_score", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "__TypeKind", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "name", - "description": null, + "name": "output_index", + "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "description", - "description": null, + "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "fields", - "description": null, - "args": [ - { - "name": "includeDeprecated", - "description": null, - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "defaultValue": "false" - } - ], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Field", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "interfaces", - "description": null, + "name": "from", + "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, - { - "name": "possibleTypes", - "description": null, - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - } - }, + { + "name": "immutable", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "enumValues", - "description": null, - "args": [ - { - "name": "includeDeprecated", - "description": null, - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "defaultValue": "false" - } - ], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__EnumValue", "ofType": null } - } - }, + "name": "index", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "inputFields", - "description": null, + "name": "interval", + "description": "", "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ofType", - "description": null, + "name": "language", + "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "__TypeKind", - "description": "An enum describing what kind of type a given `__Type` is.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ + }, { - "name": "SCALAR", - "description": "Indicates this type is a scalar.", + "name": "query", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "OBJECT", - "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "name": "references", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "INTERFACE", - "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "name": "severity", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "UNION", - "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "name": "tags", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ENUM", - "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "name": "threat", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "INPUT_OBJECT", - "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "name": "type", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "LIST", - "description": "Indicates this type is a list. `ofType` is a valid field.", + "name": "size", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "NON_NULL", - "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "name": "to", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Field", - "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", - "fields": [ + }, { - "name": "name", - "description": null, + "name": "enabled", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToBooleanArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "description", - "description": null, + "name": "filters", + "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "args", - "description": null, + "name": "created_at", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } - } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "type", - "description": null, + "name": "updated_at", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "isDeprecated", - "description": null, + "name": "created_by", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "deprecationReason", - "description": null, + "name": "updated_by", + "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__InputValue", - "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", - "fields": [ + }, { - "name": "name", - "description": null, + "name": "version", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "description", - "description": null, + "name": "note", + "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "type", - "description": null, + "name": "threshold", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "defaultValue", - "description": "A GraphQL-formatted string representing the default value for this input value.", + "name": "exceptions_list", + "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToAny", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -12502,46 +7438,49 @@ }, { "kind": "OBJECT", - "name": "__EnumValue", - "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "name": "SignalField", + "description": "", "fields": [ { - "name": "name", - "description": null, + "name": "rule", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "RuleField", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "description", - "description": null, + "name": "original_time", + "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "isDeprecated", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, + "name": "status", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "RuleEcsField", + "description": "", + "fields": [ { - "name": "deprecationReason", - "description": null, + "name": "reference", + "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -12553,12 +7492,12 @@ }, { "kind": "OBJECT", - "name": "__Directive", - "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "name": "ECS", + "description": "", "fields": [ { - "name": "name", - "description": null, + "name": "_id", + "description": "", "args": [], "type": { "kind": "NON_NULL", @@ -12569,240 +7508,202 @@ "deprecationReason": null }, { - "name": "description", - "description": null, + "name": "_index", + "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "locations", - "description": null, + "name": "agent", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "__DirectiveLocation", "ofType": null } - } - } - }, + "type": { "kind": "OBJECT", "name": "AgentEcsField", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "args", - "description": null, + "name": "auditd", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } - } - } - }, + "type": { "kind": "OBJECT", "name": "AuditdEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "onOperation", - "description": null, + "name": "destination", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": true, - "deprecationReason": "Use `locations`." + "type": { "kind": "OBJECT", "name": "DestinationEcsFields", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "onFragment", - "description": null, + "name": "dns", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": true, - "deprecationReason": "Use `locations`." + "type": { "kind": "OBJECT", "name": "DnsEcsFields", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "onField", - "description": null, + "name": "endgame", + "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": true, - "deprecationReason": "Use `locations`." - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "__DirectiveLocation", - "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "QUERY", - "description": "Location adjacent to a query operation.", + "type": { "kind": "OBJECT", "name": "EndgameEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "MUTATION", - "description": "Location adjacent to a mutation operation.", + "name": "event", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "EventEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "SUBSCRIPTION", - "description": "Location adjacent to a subscription operation.", + "name": "geo", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "GeoEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "FIELD", - "description": "Location adjacent to a field.", + "name": "host", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "HostEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "FRAGMENT_DEFINITION", - "description": "Location adjacent to a fragment definition.", + "name": "network", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "NetworkEcsField", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "FRAGMENT_SPREAD", - "description": "Location adjacent to a fragment spread.", + "name": "rule", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "RuleEcsField", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "INLINE_FRAGMENT", - "description": "Location adjacent to an inline fragment.", + "name": "signal", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "SignalField", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "SCHEMA", - "description": "Location adjacent to a schema definition.", + "name": "source", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "SourceEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "SCALAR", - "description": "Location adjacent to a scalar definition.", + "name": "suricata", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "SuricataEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "OBJECT", - "description": "Location adjacent to an object type definition.", + "name": "tls", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "TlsEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "FIELD_DEFINITION", - "description": "Location adjacent to a field definition.", + "name": "zeek", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "ZeekEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ARGUMENT_DEFINITION", - "description": "Location adjacent to an argument definition.", + "name": "http", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "HttpEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "INTERFACE", - "description": "Location adjacent to an interface definition.", + "name": "url", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "UrlEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "UNION", - "description": "Location adjacent to a union definition.", + "name": "timestamp", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ENUM", - "description": "Location adjacent to an enum definition.", + "name": "message", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ENUM_VALUE", - "description": "Location adjacent to an enum value definition.", + "name": "user", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "UserEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "INPUT_OBJECT", - "description": "Location adjacent to an input object type definition.", + "name": "winlog", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "WinlogEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "INPUT_FIELD_DEFINITION", - "description": "Location adjacent to an input object field definition.", + "name": "process", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "ProcessEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "EcsEdges", - "description": "", - "fields": [ + }, { - "name": "node", + "name": "file", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "ECS", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "FileFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "cursor", + "name": "system", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "SystemEcsField", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -12814,60 +7715,32 @@ }, { "kind": "OBJECT", - "name": "EventsTimelineData", + "name": "EcsEdges", "description": "", "fields": [ { - "name": "edges", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "EcsEdges", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "totalCount", + "name": "node", "description": "", "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "ECS", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "pageInfo", + "name": "cursor", "description": "", "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } }, "isDeprecated": false, "deprecationReason": null - }, - { - "name": "inspect", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null } ], "inputFields": null, @@ -13010,128 +7883,292 @@ "possibleTypes": null }, { - "kind": "ENUM", - "name": "NetworkDirectionEcs", + "kind": "SCALAR", + "name": "ToIFieldSubTypeNonNullable", "description": "", "fields": null, "inputFields": null, "interfaces": null, - "enumValues": [ + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "IndexField", + "description": "A descriptor of a field in an index", + "fields": [ { - "name": "inbound", - "description": "", + "name": "category", + "description": "Where the field belong", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "outbound", - "description": "", + "name": "example", + "description": "Example of field's value", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "internal", - "description": "", + "name": "indexes", + "description": "whether the field's belong to an alias index", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "external", - "description": "", + "name": "name", + "description": "The name of the field", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "incoming", - "description": "", + "name": "type", + "description": "The type of the field's values as recognized by Kibana", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "searchable", + "description": "Whether the field's values can be efficiently searched for", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "aggregatable", + "description": "Whether the field's values can be aggregated", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": "Description of the field", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "outgoing", + "name": "format", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "esTypes", + "description": "the elastic type as mapped in the index", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArrayNoNullable", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "listening", + "name": "subType", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToIFieldSubTypeNonNullable", "ofType": null }, "isDeprecated": false, "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "PaginationInput", + "description": "", + "fields": null, + "inputFields": [ + { + "name": "limit", + "description": "The limit parameter allows you to configure the maximum amount of items to be returned", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "defaultValue": null }, - { "name": "unknown", "description": "", "isDeprecated": false, "deprecationReason": null } + { + "name": "cursor", + "description": "The cursor parameter defines the next result you want to fetch", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "tiebreaker", + "description": "The tiebreaker parameter allow to be more precise to fetch the next item", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + } ], + "interfaces": null, + "enumValues": null, "possibleTypes": null }, { "kind": "ENUM", - "name": "NetworkHttpFields", + "name": "FlowTarget", "description": "", "fields": null, "inputFields": null, "interfaces": null, "enumValues": [ + { "name": "client", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "domains", + "name": "destination", "description": "", "isDeprecated": false, "deprecationReason": null }, + { "name": "server", "description": "", "isDeprecated": false, "deprecationReason": null }, + { "name": "source", "description": "", "isDeprecated": false, "deprecationReason": null } + ], + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "FlowTargetSourceDest", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ { - "name": "lastHost", + "name": "destination", "description": "", "isDeprecated": false, "deprecationReason": null }, + { "name": "source", "description": "", "isDeprecated": false, "deprecationReason": null } + ], + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "FlowDirection", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ { - "name": "lastSourceIp", + "name": "uniDirectional", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "methods", + "name": "biDirectional", "description": "", "isDeprecated": false, "deprecationReason": null - }, - { "name": "path", "description": "", "isDeprecated": false, "deprecationReason": null }, + } + ], + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "SortField", + "description": "", + "fields": null, + "inputFields": [ { - "name": "requestCount", + "name": "sortFieldId", "description": "", - "isDeprecated": false, - "deprecationReason": null + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "defaultValue": null }, { - "name": "statuses", + "name": "direction", "description": "", - "isDeprecated": false, - "deprecationReason": null + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } + }, + "defaultValue": null } ], + "interfaces": null, + "enumValues": null, "possibleTypes": null }, { - "kind": "ENUM", - "name": "FlowDirection", + "kind": "OBJECT", + "name": "PageInfo", "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ + "fields": [ { - "name": "uniDirectional", + "name": "endCursor", "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "CursorType", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "biDirectional", + "name": "hasNextPage", "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], + "inputFields": null, + "interfaces": [], + "enumValues": null, "possibleTypes": null }, { diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts index 65d9212f77dcc..df8333ea63055 100644 --- a/x-pack/plugins/security_solution/public/graphql/types.ts +++ b/x-pack/plugins/security_solution/public/graphql/types.ts @@ -40,65 +40,16 @@ export interface PaginationInputPaginated { querySize: number; } -export interface DocValueFieldsInput { - field: string; - - format: string; -} - -export interface PaginationInput { - /** The limit parameter allows you to configure the maximum amount of items to be returned */ - limit: number; - /** The cursor parameter defines the next result you want to fetch */ - cursor?: Maybe; - /** The tiebreaker parameter allow to be more precise to fetch the next item */ - tiebreaker?: Maybe; -} - -export interface SortField { - sortFieldId: string; - - direction: Direction; -} - -export interface LastTimeDetails { - hostName?: Maybe; - - ip?: Maybe; -} - export interface HostsSortField { field: HostsFields; direction: Direction; } -export interface UsersSortField { - field: UsersFields; - - direction: Direction; -} - -export interface NetworkTopTablesSortField { - field: NetworkTopTablesFields; - - direction: Direction; -} - -export interface NetworkDnsSortField { - field: NetworkDnsFields; - - direction: Direction; -} - -export interface NetworkHttpSortField { - direction: Direction; -} - -export interface TlsSortField { - field: TlsFields; +export interface DocValueFieldsInput { + field: string; - direction: Direction; + format: string; } export interface PageInfoTimeline { @@ -138,6 +89,8 @@ export interface TimelineInput { kqlQuery?: Maybe; + indexNames?: Maybe; + title?: Maybe; templateTimelineId?: Maybe; @@ -277,6 +230,21 @@ export interface SortTimelineInput { sortDirection?: Maybe; } +export interface PaginationInput { + /** The limit parameter allows you to configure the maximum amount of items to be returned */ + limit: number; + /** The cursor parameter defines the next result you want to fetch */ + cursor?: Maybe; + /** The tiebreaker parameter allow to be more precise to fetch the next item */ + tiebreaker?: Maybe; +} + +export interface SortField { + sortFieldId: string; + + direction: Direction; +} + export interface FavoriteTimelineInput { fullName?: Maybe; @@ -295,13 +263,6 @@ export enum Direction { desc = 'desc', } -export enum LastEventIndexKey { - hostDetails = 'hostDetails', - hosts = 'hosts', - ipDetails = 'ipDetails', - network = 'network', -} - export enum HostsFields { hostName = 'hostName', lastSeen = 'lastSeen', @@ -313,51 +274,6 @@ export enum HostPolicyResponseActionStatus { warning = 'warning', } -export enum UsersFields { - name = 'name', - count = 'count', -} - -export enum FlowTarget { - client = 'client', - destination = 'destination', - server = 'server', - source = 'source', -} - -export enum HistogramType { - authentications = 'authentications', - anomalies = 'anomalies', - events = 'events', - alerts = 'alerts', - dns = 'dns', -} - -export enum FlowTargetSourceDest { - destination = 'destination', - source = 'source', -} - -export enum NetworkTopTablesFields { - bytes_in = 'bytes_in', - bytes_out = 'bytes_out', - flows = 'flows', - destination_ips = 'destination_ips', - source_ips = 'source_ips', -} - -export enum NetworkDnsFields { - dnsName = 'dnsName', - queryCount = 'queryCount', - uniqueDomains = 'uniqueDomains', - dnsBytesIn = 'dnsBytesIn', - dnsBytesOut = 'dnsBytesOut', -} - -export enum TlsFields { - _id = '_id', -} - export enum DataProviderType { default = 'default', template = 'template', @@ -397,25 +313,16 @@ export enum SortFieldTimeline { created = 'created', } -export enum NetworkDirectionEcs { - inbound = 'inbound', - outbound = 'outbound', - internal = 'internal', - external = 'external', - incoming = 'incoming', - outgoing = 'outgoing', - listening = 'listening', - unknown = 'unknown', +export enum FlowTarget { + client = 'client', + destination = 'destination', + server = 'server', + source = 'source', } -export enum NetworkHttpFields { - domains = 'domains', - lastHost = 'lastHost', - lastSourceIp = 'lastSourceIp', - methods = 'methods', - path = 'path', - requestCount = 'requestCount', - statuses = 'statuses', +export enum FlowTargetSourceDest { + destination = 'destination', + source = 'source', } export enum FlowDirection { @@ -423,23 +330,21 @@ export enum FlowDirection { biDirectional = 'biDirectional', } -export type ToStringArrayNoNullable = any; - -export type ToIFieldSubTypeNonNullable = any; - export type ToStringArray = string[]; export type Date = string; -export type ToNumberArray = number[]; +export type ToAny = any; + +export type ToStringArrayNoNullable = any; export type ToDateArray = string[]; -export type ToBooleanArray = boolean[]; +export type ToNumberArray = number[]; -export type ToAny = any; +export type ToBooleanArray = boolean[]; -export type EsValue = any; +export type ToIFieldSubTypeNonNullable = any; // ==================================================== // Scalars @@ -528,52 +433,12 @@ export interface Source { configuration: SourceConfiguration; /** The status of the source */ status: SourceStatus; - /** Gets Authentication success and failures based on a timerange */ - Authentications: AuthenticationsData; - - Timeline: TimelineData; - - TimelineDetails: TimelineDetailsData; - - LastEventTime: LastEventTimeData; /** Gets Hosts based on timerange and specified criteria, or all events in the timerange if no criteria is specified */ Hosts: HostsData; HostOverview: HostItem; HostFirstLastSeen: FirstLastSeenHost; - - IpOverview?: Maybe; - - Users: UsersData; - - KpiNetwork?: Maybe; - - KpiHosts: KpiHostsData; - - KpiHostDetails: KpiHostDetailsData; - - MatrixHistogram: MatrixHistogramOverTimeData; - - NetworkTopCountries: NetworkTopCountriesData; - - NetworkTopNFlow: NetworkTopNFlowData; - - NetworkDns: NetworkDnsData; - - NetworkDnsHistogram: NetworkDsOverTimeData; - - NetworkHttp: NetworkHttpData; - - OverviewNetwork?: Maybe; - - OverviewHost?: Maybe; - - Tls: TlsData; - /** Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified */ - UncommonProcesses: UncommonProcessesData; - /** Just a simple example to get the app name */ - whoAmI?: Maybe; } /** A set of configuration options for a security data source */ @@ -603,37 +468,11 @@ export interface SourceStatus { /** Whether the configured alias or wildcard pattern resolve to any auditbeat indices */ indicesExist: boolean; /** The list of fields defined in the index mappings */ - indexFields: IndexField[]; -} - -/** A descriptor of a field in an index */ -export interface IndexField { - /** Where the field belong */ - category: string; - /** Example of field's value */ - example?: Maybe; - /** whether the field's belong to an alias index */ - indexes: (Maybe)[]; - /** The name of the field */ - name: string; - /** The type of the field's values as recognized by Kibana */ - type: string; - /** Whether the field's values can be efficiently searched for */ - searchable: boolean; - /** Whether the field's values can be aggregated */ - aggregatable: boolean; - /** Description of the field */ - description?: Maybe; - - format?: Maybe; - /** the elastic type as mapped in the index */ - esTypes?: Maybe; - - subType?: Maybe; + indexFields: string[]; } -export interface AuthenticationsData { - edges: AuthenticationsEdges[]; +export interface HostsData { + edges: HostsEdges[]; totalCount: number; @@ -642,84 +481,50 @@ export interface AuthenticationsData { inspect?: Maybe; } -export interface AuthenticationsEdges { - node: AuthenticationItem; +export interface HostsEdges { + node: HostItem; cursor: CursorType; } -export interface AuthenticationItem { - _id: string; +export interface HostItem { + _id?: Maybe; - failures: number; + cloud?: Maybe; - successes: number; + endpoint?: Maybe; - user: UserEcsFields; + host?: Maybe; - lastSuccess?: Maybe; + inspect?: Maybe; - lastFailure?: Maybe; + lastSeen?: Maybe; } -export interface UserEcsFields { - domain?: Maybe; - - id?: Maybe; - - name?: Maybe; - - full_name?: Maybe; +export interface CloudFields { + instance?: Maybe; - email?: Maybe; + machine?: Maybe; - hash?: Maybe; + provider?: Maybe<(Maybe)[]>; - group?: Maybe; + region?: Maybe<(Maybe)[]>; } -export interface LastSourceHost { - timestamp?: Maybe; - - source?: Maybe; - - host?: Maybe; +export interface CloudInstance { + id?: Maybe<(Maybe)[]>; } -export interface SourceEcsFields { - bytes?: Maybe; - - ip?: Maybe; - - port?: Maybe; - - domain?: Maybe; - - geo?: Maybe; - - packets?: Maybe; +export interface CloudMachine { + type?: Maybe<(Maybe)[]>; } -export interface GeoEcsFields { - city_name?: Maybe; - - continent_name?: Maybe; - - country_iso_code?: Maybe; - - country_name?: Maybe; - - location?: Maybe; - - region_iso_code?: Maybe; - - region_name?: Maybe; -} +export interface EndpointFields { + endpointPolicy?: Maybe; -export interface Location { - lon?: Maybe; + sensorVersion?: Maybe; - lat?: Maybe; + policyStatus?: Maybe; } export interface HostEcsFields { @@ -752,6 +557,12 @@ export interface OsEcsFields { kernel?: Maybe; } +export interface Inspect { + dsl: string[]; + + response: string[]; +} + export interface CursorType { value?: Maybe; @@ -766,196 +577,267 @@ export interface PageInfoPaginated { showMorePagesIndicator: boolean; } -export interface Inspect { - dsl: string[]; +export interface FirstLastSeenHost { + inspect?: Maybe; - response: string[]; + firstSeen?: Maybe; + + lastSeen?: Maybe; } -export interface TimelineData { - edges: TimelineEdges[]; +export interface TimelineResult { + columns?: Maybe; - totalCount: number; + created?: Maybe; - pageInfo: PageInfo; + createdBy?: Maybe; - inspect?: Maybe; -} + dataProviders?: Maybe; -export interface TimelineEdges { - node: TimelineItem; + dateRange?: Maybe; - cursor: CursorType; -} + description?: Maybe; -export interface TimelineItem { - _id: string; + eventIdToNoteIds?: Maybe; - _index?: Maybe; + eventType?: Maybe; - data: TimelineNonEcsData[]; + excludedRowRendererIds?: Maybe; - ecs: Ecs; -} + favorite?: Maybe; -export interface TimelineNonEcsData { - field: string; + filters?: Maybe; - value?: Maybe; -} + kqlMode?: Maybe; -export interface Ecs { - _id: string; + kqlQuery?: Maybe; - _index?: Maybe; + indexNames?: Maybe; - agent?: Maybe; + notes?: Maybe; - auditd?: Maybe; + noteIds?: Maybe; - destination?: Maybe; + pinnedEventIds?: Maybe; - dns?: Maybe; + pinnedEventsSaveObject?: Maybe; - endgame?: Maybe; + savedQueryId?: Maybe; - event?: Maybe; + savedObjectId: string; - geo?: Maybe; + sort?: Maybe; - host?: Maybe; + status?: Maybe; - network?: Maybe; + title?: Maybe; - rule?: Maybe; + templateTimelineId?: Maybe; - signal?: Maybe; + templateTimelineVersion?: Maybe; - source?: Maybe; + timelineType?: Maybe; - suricata?: Maybe; + updated?: Maybe; - tls?: Maybe; + updatedBy?: Maybe; - zeek?: Maybe; + version: string; +} - http?: Maybe; +export interface ColumnHeaderResult { + aggregatable?: Maybe; - url?: Maybe; + category?: Maybe; - timestamp?: Maybe; + columnHeaderType?: Maybe; - message?: Maybe; + description?: Maybe; - user?: Maybe; + example?: Maybe; - winlog?: Maybe; + indexes?: Maybe; - process?: Maybe; + id?: Maybe; - file?: Maybe; + name?: Maybe; - system?: Maybe; -} + placeholder?: Maybe; -export interface AgentEcsField { - type?: Maybe; + searchable?: Maybe; + + type?: Maybe; } -export interface AuditdEcsFields { - result?: Maybe; +export interface DataProviderResult { + id?: Maybe; - session?: Maybe; + name?: Maybe; - data?: Maybe; + enabled?: Maybe; - summary?: Maybe; + excluded?: Maybe; - sequence?: Maybe; + kqlQuery?: Maybe; + + queryMatch?: Maybe; + + type?: Maybe; + + and?: Maybe; } -export interface AuditdData { - acct?: Maybe; +export interface QueryMatchResult { + field?: Maybe; - terminal?: Maybe; + displayField?: Maybe; - op?: Maybe; + value?: Maybe; + + displayValue?: Maybe; + + operator?: Maybe; } -export interface Summary { - actor?: Maybe; +export interface DateRangePickerResult { + start?: Maybe; - object?: Maybe; + end?: Maybe; +} - how?: Maybe; +export interface FavoriteTimelineResult { + fullName?: Maybe; - message_type?: Maybe; + userName?: Maybe; - sequence?: Maybe; + favoriteDate?: Maybe; } -export interface PrimarySecondary { - primary?: Maybe; +export interface FilterTimelineResult { + exists?: Maybe; - secondary?: Maybe; + meta?: Maybe; - type?: Maybe; + match_all?: Maybe; + + missing?: Maybe; + + query?: Maybe; + + range?: Maybe; + + script?: Maybe; } -export interface DestinationEcsFields { - bytes?: Maybe; +export interface FilterMetaTimelineResult { + alias?: Maybe; - ip?: Maybe; + controlledBy?: Maybe; - port?: Maybe; + disabled?: Maybe; - domain?: Maybe; + field?: Maybe; - geo?: Maybe; + formattedValue?: Maybe; - packets?: Maybe; + index?: Maybe; + + key?: Maybe; + + negate?: Maybe; + + params?: Maybe; + + type?: Maybe; + + value?: Maybe; } -export interface DnsEcsFields { - question?: Maybe; +export interface SerializedFilterQueryResult { + filterQuery?: Maybe; +} - resolved_ip?: Maybe; +export interface SerializedKueryQueryResult { + kuery?: Maybe; - response_code?: Maybe; + serializedQuery?: Maybe; } -export interface DnsQuestionData { - name?: Maybe; +export interface KueryFilterQueryResult { + kind?: Maybe; - type?: Maybe; + expression?: Maybe; } -export interface EndgameEcsFields { - exit_code?: Maybe; +export interface SortTimelineResult { + columnId?: Maybe; - file_name?: Maybe; + sortDirection?: Maybe; +} - file_path?: Maybe; +export interface ResponseTimelines { + timeline: (Maybe)[]; - logon_type?: Maybe; + totalCount?: Maybe; - parent_process_name?: Maybe; + defaultTimelineCount?: Maybe; - pid?: Maybe; + templateTimelineCount?: Maybe; - process_name?: Maybe; + elasticTemplateTimelineCount?: Maybe; - subject_domain_name?: Maybe; + customTemplateTimelineCount?: Maybe; - subject_logon_id?: Maybe; + favoriteCount?: Maybe; +} - subject_user_name?: Maybe; +export interface Mutation { + /** Persists a note */ + persistNote: ResponseNote; - target_domain_name?: Maybe; + deleteNote?: Maybe; - target_logon_id?: Maybe; + deleteNoteByTimelineId?: Maybe; + /** Persists a pinned event in a timeline */ + persistPinnedEventOnTimeline?: Maybe; + /** Remove a pinned events in a timeline */ + deletePinnedEventOnTimeline: boolean; + /** Remove all pinned events in a timeline */ + deleteAllPinnedEventsOnTimeline: boolean; + /** Persists a timeline */ + persistTimeline: ResponseTimeline; - target_user_name?: Maybe; + persistFavorite: ResponseFavoriteTimeline; + + deleteTimeline: boolean; +} + +export interface ResponseNote { + code?: Maybe; + + message?: Maybe; + + note: NoteResult; +} + +export interface ResponseTimeline { + code?: Maybe; + + message?: Maybe; + + timeline: TimelineResult; +} + +export interface ResponseFavoriteTimeline { + code?: Maybe; + + message?: Maybe; + + savedObjectId: string; + + version: string; + + favorite?: Maybe; } export interface EventEcsFields { @@ -998,110 +880,176 @@ export interface EventEcsFields { type?: Maybe; } -export interface NetworkEcsField { - bytes?: Maybe; - - community_id?: Maybe; +export interface Location { + lon?: Maybe; - direction?: Maybe; + lat?: Maybe; +} - packets?: Maybe; +export interface GeoEcsFields { + city_name?: Maybe; - protocol?: Maybe; + continent_name?: Maybe; - transport?: Maybe; -} + country_iso_code?: Maybe; -export interface RuleEcsField { - reference?: Maybe; -} + country_name?: Maybe; -export interface SignalField { - rule?: Maybe; + location?: Maybe; - original_time?: Maybe; + region_iso_code?: Maybe; - status?: Maybe; + region_name?: Maybe; } -export interface RuleField { - id?: Maybe; +export interface PrimarySecondary { + primary?: Maybe; - rule_id?: Maybe; + secondary?: Maybe; - false_positives: string[]; + type?: Maybe; +} - saved_id?: Maybe; +export interface Summary { + actor?: Maybe; - timeline_id?: Maybe; + object?: Maybe; - timeline_title?: Maybe; + how?: Maybe; - max_signals?: Maybe; + message_type?: Maybe; - risk_score?: Maybe; + sequence?: Maybe; +} - output_index?: Maybe; +export interface AgentEcsField { + type?: Maybe; +} - description?: Maybe; +export interface AuditdData { + acct?: Maybe; - from?: Maybe; + terminal?: Maybe; - immutable?: Maybe; + op?: Maybe; +} - index?: Maybe; +export interface AuditdEcsFields { + result?: Maybe; - interval?: Maybe; + session?: Maybe; - language?: Maybe; + data?: Maybe; - query?: Maybe; + summary?: Maybe; - references?: Maybe; + sequence?: Maybe; +} - severity?: Maybe; +export interface Thread { + id?: Maybe; - tags?: Maybe; + start?: Maybe; +} - threat?: Maybe; +export interface ProcessHashData { + md5?: Maybe; - type?: Maybe; + sha1?: Maybe; - size?: Maybe; + sha256?: Maybe; +} - to?: Maybe; +export interface ProcessEcsFields { + hash?: Maybe; - enabled?: Maybe; + pid?: Maybe; - filters?: Maybe; + name?: Maybe; - created_at?: Maybe; + ppid?: Maybe; - updated_at?: Maybe; + args?: Maybe; - created_by?: Maybe; + entity_id?: Maybe; - updated_by?: Maybe; + executable?: Maybe; - version?: Maybe; + title?: Maybe; - note?: Maybe; + thread?: Maybe; - threshold?: Maybe; + working_directory?: Maybe; +} - exceptions_list?: Maybe; +export interface SourceEcsFields { + bytes?: Maybe; + + ip?: Maybe; + + port?: Maybe; + + domain?: Maybe; + + geo?: Maybe; + + packets?: Maybe; } -export interface SuricataEcsFields { - eve?: Maybe; +export interface DestinationEcsFields { + bytes?: Maybe; + + ip?: Maybe; + + port?: Maybe; + + domain?: Maybe; + + geo?: Maybe; + + packets?: Maybe; } -export interface SuricataEveData { - alert?: Maybe; +export interface DnsQuestionData { + name?: Maybe; - flow_id?: Maybe; + type?: Maybe; +} - proto?: Maybe; +export interface DnsEcsFields { + question?: Maybe; + + resolved_ip?: Maybe; + + response_code?: Maybe; +} + +export interface EndgameEcsFields { + exit_code?: Maybe; + + file_name?: Maybe; + + file_path?: Maybe; + + logon_type?: Maybe; + + parent_process_name?: Maybe; + + pid?: Maybe; + + process_name?: Maybe; + + subject_domain_name?: Maybe; + + subject_logon_id?: Maybe; + + subject_user_name?: Maybe; + + target_domain_name?: Maybe; + + target_logon_id?: Maybe; + + target_user_name?: Maybe; } export interface SuricataAlertData { @@ -1110,48 +1058,44 @@ export interface SuricataAlertData { signature_id?: Maybe; } -export interface TlsEcsFields { - client_certificate?: Maybe; +export interface SuricataEveData { + alert?: Maybe; - fingerprints?: Maybe; + flow_id?: Maybe; - server_certificate?: Maybe; + proto?: Maybe; } -export interface TlsClientCertificateData { - fingerprint?: Maybe; +export interface SuricataEcsFields { + eve?: Maybe; } -export interface FingerprintData { - sha1?: Maybe; +export interface TlsJa3Data { + hash?: Maybe; } -export interface TlsFingerprintsData { - ja3?: Maybe; +export interface FingerprintData { + sha1?: Maybe; } -export interface TlsJa3Data { - hash?: Maybe; +export interface TlsClientCertificateData { + fingerprint?: Maybe; } export interface TlsServerCertificateData { fingerprint?: Maybe; } -export interface ZeekEcsFields { - session_id?: Maybe; - - connection?: Maybe; - - notice?: Maybe; - - dns?: Maybe; +export interface TlsFingerprintsData { + ja3?: Maybe; +} - http?: Maybe; +export interface TlsEcsFields { + client_certificate?: Maybe; - files?: Maybe; + fingerprints?: Maybe; - ssl?: Maybe; + server_certificate?: Maybe; } export interface ZeekConnectionData { @@ -1206,96 +1150,80 @@ export interface ZeekDnsData { TC?: Maybe; } -export interface ZeekHttpData { - resp_mime_types?: Maybe; - - trans_depth?: Maybe; +export interface FileFields { + name?: Maybe; - status_msg?: Maybe; + path?: Maybe; - resp_fuids?: Maybe; + target_path?: Maybe; - tags?: Maybe; -} + extension?: Maybe; -export interface ZeekFileData { - session_ids?: Maybe; + type?: Maybe; - timedout?: Maybe; + device?: Maybe; - local_orig?: Maybe; + inode?: Maybe; - tx_host?: Maybe; + uid?: Maybe; - source?: Maybe; + owner?: Maybe; - is_orig?: Maybe; + gid?: Maybe; - overflow_bytes?: Maybe; + group?: Maybe; - sha1?: Maybe; + mode?: Maybe; - duration?: Maybe; + size?: Maybe; - depth?: Maybe; + mtime?: Maybe; - analyzers?: Maybe; + ctime?: Maybe; +} - mime_type?: Maybe; +export interface ZeekHttpData { + resp_mime_types?: Maybe; - rx_host?: Maybe; + trans_depth?: Maybe; - total_bytes?: Maybe; + status_msg?: Maybe; - fuid?: Maybe; + resp_fuids?: Maybe; - seen_bytes?: Maybe; + tags?: Maybe; +} - missing_bytes?: Maybe; +export interface HttpBodyData { + content?: Maybe; - md5?: Maybe; + bytes?: Maybe; } -export interface ZeekSslData { - cipher?: Maybe; +export interface HttpRequestData { + method?: Maybe; - established?: Maybe; + body?: Maybe; - resumed?: Maybe; + referrer?: Maybe; - version?: Maybe; -} - -export interface HttpEcsFields { - version?: Maybe; - - request?: Maybe; - - response?: Maybe; + bytes?: Maybe; } -export interface HttpRequestData { - method?: Maybe; +export interface HttpResponseData { + status_code?: Maybe; body?: Maybe; - referrer?: Maybe; - bytes?: Maybe; } -export interface HttpBodyData { - content?: Maybe; - - bytes?: Maybe; -} - -export interface HttpResponseData { - status_code?: Maybe; +export interface HttpEcsFields { + version?: Maybe; - body?: Maybe; + request?: Maybe; - bytes?: Maybe; + response?: Maybe; } export interface UrlEcsFields { @@ -1308,86 +1236,102 @@ export interface UrlEcsFields { password?: Maybe; } -export interface WinlogEcsFields { - event_id?: Maybe; -} +export interface ZeekFileData { + session_ids?: Maybe; -export interface ProcessEcsFields { - hash?: Maybe; + timedout?: Maybe; - pid?: Maybe; + local_orig?: Maybe; - name?: Maybe; + tx_host?: Maybe; - ppid?: Maybe; + source?: Maybe; - args?: Maybe; + is_orig?: Maybe; - entity_id?: Maybe; + overflow_bytes?: Maybe; - executable?: Maybe; + sha1?: Maybe; - title?: Maybe; + duration?: Maybe; - thread?: Maybe; + depth?: Maybe; - working_directory?: Maybe; -} + analyzers?: Maybe; -export interface ProcessHashData { - md5?: Maybe; + mime_type?: Maybe; - sha1?: Maybe; + rx_host?: Maybe; - sha256?: Maybe; + total_bytes?: Maybe; + + fuid?: Maybe; + + seen_bytes?: Maybe; + + missing_bytes?: Maybe; + + md5?: Maybe; } -export interface Thread { - id?: Maybe; +export interface ZeekSslData { + cipher?: Maybe; - start?: Maybe; + established?: Maybe; + + resumed?: Maybe; + + version?: Maybe; } -export interface FileFields { - name?: Maybe; +export interface ZeekEcsFields { + session_id?: Maybe; - path?: Maybe; + connection?: Maybe; - target_path?: Maybe; + notice?: Maybe; - extension?: Maybe; + dns?: Maybe; - type?: Maybe; + http?: Maybe; - device?: Maybe; + files?: Maybe; - inode?: Maybe; + ssl?: Maybe; +} - uid?: Maybe; +export interface UserEcsFields { + domain?: Maybe; - owner?: Maybe; + id?: Maybe; - gid?: Maybe; + name?: Maybe; - group?: Maybe; + full_name?: Maybe; - mode?: Maybe; + email?: Maybe; - size?: Maybe; + hash?: Maybe; - mtime?: Maybe; + group?: Maybe; +} - ctime?: Maybe; +export interface WinlogEcsFields { + event_id?: Maybe; } -export interface SystemEcsField { - audit?: Maybe; +export interface NetworkEcsField { + bytes?: Maybe; - auth?: Maybe; -} + community_id?: Maybe; -export interface AuditEcsFields { - package?: Maybe; + direction?: Maybe; + + packets?: Maybe; + + protocol?: Maybe; + + transport?: Maybe; } export interface PackageEcsFields { @@ -1404,8 +1348,8 @@ export interface PackageEcsFields { version?: Maybe; } -export interface AuthEcsFields { - ssh?: Maybe; +export interface AuditEcsFields { + package?: Maybe; } export interface SshEcsFields { @@ -1414,4061 +1358,760 @@ export interface SshEcsFields { signature?: Maybe; } -export interface PageInfo { - endCursor?: Maybe; - - hasNextPage?: Maybe; +export interface AuthEcsFields { + ssh?: Maybe; } -export interface TimelineDetailsData { - data?: Maybe; +export interface SystemEcsField { + audit?: Maybe; - inspect?: Maybe; + auth?: Maybe; } -export interface DetailItem { - field: string; - - values?: Maybe; - - originalValue?: Maybe; -} +export interface RuleField { + id?: Maybe; -export interface LastEventTimeData { - lastSeen?: Maybe; + rule_id?: Maybe; - inspect?: Maybe; -} + false_positives: string[]; -export interface HostsData { - edges: HostsEdges[]; + saved_id?: Maybe; - totalCount: number; + timeline_id?: Maybe; - pageInfo: PageInfoPaginated; + timeline_title?: Maybe; - inspect?: Maybe; -} + max_signals?: Maybe; -export interface HostsEdges { - node: HostItem; + risk_score?: Maybe; - cursor: CursorType; -} + output_index?: Maybe; -export interface HostItem { - _id?: Maybe; + description?: Maybe; - cloud?: Maybe; + from?: Maybe; - endpoint?: Maybe; + immutable?: Maybe; - host?: Maybe; + index?: Maybe; - inspect?: Maybe; + interval?: Maybe; - lastSeen?: Maybe; -} + language?: Maybe; -export interface CloudFields { - instance?: Maybe; + query?: Maybe; - machine?: Maybe; + references?: Maybe; - provider?: Maybe<(Maybe)[]>; + severity?: Maybe; - region?: Maybe<(Maybe)[]>; -} + tags?: Maybe; -export interface CloudInstance { - id?: Maybe<(Maybe)[]>; -} + threat?: Maybe; -export interface CloudMachine { - type?: Maybe<(Maybe)[]>; -} + type?: Maybe; -export interface EndpointFields { - endpointPolicy?: Maybe; + size?: Maybe; - sensorVersion?: Maybe; + to?: Maybe; - policyStatus?: Maybe; -} + enabled?: Maybe; -export interface FirstLastSeenHost { - inspect?: Maybe; + filters?: Maybe; - firstSeen?: Maybe; + created_at?: Maybe; - lastSeen?: Maybe; -} + updated_at?: Maybe; -export interface IpOverviewData { - client?: Maybe; + created_by?: Maybe; - destination?: Maybe; + updated_by?: Maybe; - host: HostEcsFields; + version?: Maybe; - server?: Maybe; + note?: Maybe; - source?: Maybe; + threshold?: Maybe; - inspect?: Maybe; + exceptions_list?: Maybe; } -export interface Overview { - firstSeen?: Maybe; - - lastSeen?: Maybe; +export interface SignalField { + rule?: Maybe; - autonomousSystem: AutonomousSystem; + original_time?: Maybe; - geo: GeoEcsFields; + status?: Maybe; } -export interface AutonomousSystem { - number?: Maybe; - - organization?: Maybe; +export interface RuleEcsField { + reference?: Maybe; } -export interface AutonomousSystemOrganization { - name?: Maybe; -} +export interface Ecs { + _id: string; -export interface UsersData { - edges: UsersEdges[]; + _index?: Maybe; - totalCount: number; + agent?: Maybe; - pageInfo: PageInfoPaginated; + auditd?: Maybe; - inspect?: Maybe; -} + destination?: Maybe; -export interface UsersEdges { - node: UsersNode; + dns?: Maybe; - cursor: CursorType; -} + endgame?: Maybe; -export interface UsersNode { - _id?: Maybe; + event?: Maybe; - timestamp?: Maybe; + geo?: Maybe; - user?: Maybe; -} + host?: Maybe; -export interface UsersItem { - name?: Maybe; + network?: Maybe; - id?: Maybe; + rule?: Maybe; - groupId?: Maybe; + signal?: Maybe; - groupName?: Maybe; + source?: Maybe; - count?: Maybe; -} + suricata?: Maybe; -export interface KpiNetworkData { - networkEvents?: Maybe; + tls?: Maybe; - uniqueFlowId?: Maybe; + zeek?: Maybe; - uniqueSourcePrivateIps?: Maybe; + http?: Maybe; - uniqueSourcePrivateIpsHistogram?: Maybe; + url?: Maybe; - uniqueDestinationPrivateIps?: Maybe; + timestamp?: Maybe; - uniqueDestinationPrivateIpsHistogram?: Maybe; + message?: Maybe; - dnsQueries?: Maybe; + user?: Maybe; - tlsHandshakes?: Maybe; + winlog?: Maybe; - inspect?: Maybe; -} + process?: Maybe; -export interface KpiNetworkHistogramData { - x?: Maybe; + file?: Maybe; - y?: Maybe; + system?: Maybe; } -export interface KpiHostsData { - hosts?: Maybe; - - hostsHistogram?: Maybe; - - authSuccess?: Maybe; - - authSuccessHistogram?: Maybe; - - authFailure?: Maybe; - - authFailureHistogram?: Maybe; +export interface EcsEdges { + node: Ecs; - uniqueSourceIps?: Maybe; + cursor: CursorType; +} - uniqueSourceIpsHistogram?: Maybe; +export interface OsFields { + platform?: Maybe; - uniqueDestinationIps?: Maybe; + name?: Maybe; - uniqueDestinationIpsHistogram?: Maybe; + full?: Maybe; - inspect?: Maybe; -} + family?: Maybe; -export interface KpiHostHistogramData { - x?: Maybe; + version?: Maybe; - y?: Maybe; + kernel?: Maybe; } -export interface KpiHostDetailsData { - authSuccess?: Maybe; - - authSuccessHistogram?: Maybe; - - authFailure?: Maybe; +export interface HostFields { + architecture?: Maybe; - authFailureHistogram?: Maybe; + id?: Maybe; - uniqueSourceIps?: Maybe; + ip?: Maybe<(Maybe)[]>; - uniqueSourceIpsHistogram?: Maybe; + mac?: Maybe<(Maybe)[]>; - uniqueDestinationIps?: Maybe; + name?: Maybe; - uniqueDestinationIpsHistogram?: Maybe; + os?: Maybe; - inspect?: Maybe; + type?: Maybe; } -export interface MatrixHistogramOverTimeData { - inspect?: Maybe; +/** A descriptor of a field in an index */ +export interface IndexField { + /** Where the field belong */ + category: string; + /** Example of field's value */ + example?: Maybe; + /** whether the field's belong to an alias index */ + indexes: (Maybe)[]; + /** The name of the field */ + name: string; + /** The type of the field's values as recognized by Kibana */ + type: string; + /** Whether the field's values can be efficiently searched for */ + searchable: boolean; + /** Whether the field's values can be aggregated */ + aggregatable: boolean; + /** Description of the field */ + description?: Maybe; - matrixHistogramData: MatrixOverTimeHistogramData[]; + format?: Maybe; + /** the elastic type as mapped in the index */ + esTypes?: Maybe; - totalCount: number; + subType?: Maybe; } -export interface MatrixOverTimeHistogramData { - x?: Maybe; - - y?: Maybe; +export interface PageInfo { + endCursor?: Maybe; - g?: Maybe; + hasNextPage?: Maybe; } -export interface NetworkTopCountriesData { - edges: NetworkTopCountriesEdges[]; - - totalCount: number; - - pageInfo: PageInfoPaginated; +// ==================================================== +// Arguments +// ==================================================== - inspect?: Maybe; +export interface GetNoteQueryArgs { + id: string; } - -export interface NetworkTopCountriesEdges { - node: NetworkTopCountriesItem; - - cursor: CursorType; +export interface GetNotesByTimelineIdQueryArgs { + timelineId: string; } - -export interface NetworkTopCountriesItem { - _id?: Maybe; - - source?: Maybe; - - destination?: Maybe; - - network?: Maybe; +export interface GetNotesByEventIdQueryArgs { + eventId: string; } +export interface GetAllNotesQueryArgs { + pageInfo?: Maybe; -export interface TopCountriesItemSource { - country?: Maybe; - - destination_ips?: Maybe; - - flows?: Maybe; - - location?: Maybe; + search?: Maybe; - source_ips?: Maybe; + sort?: Maybe; } - -export interface GeoItem { - geo?: Maybe; - - flowTarget?: Maybe; +export interface GetAllPinnedEventsByTimelineIdQueryArgs { + timelineId: string; } - -export interface TopCountriesItemDestination { - country?: Maybe; - - destination_ips?: Maybe; - - flows?: Maybe; - - location?: Maybe; - - source_ips?: Maybe; +export interface SourceQueryArgs { + /** The id of the source */ + id: string; } - -export interface TopNetworkTablesEcsField { - bytes_in?: Maybe; - - bytes_out?: Maybe; +export interface GetOneTimelineQueryArgs { + id: string; } +export interface GetAllTimelineQueryArgs { + pageInfo: PageInfoTimeline; -export interface NetworkTopNFlowData { - edges: NetworkTopNFlowEdges[]; + search?: Maybe; - totalCount: number; + sort?: Maybe; - pageInfo: PageInfoPaginated; + onlyUserFavorite?: Maybe; - inspect?: Maybe; + timelineType?: Maybe; + + status?: Maybe; } +export interface HostsSourceArgs { + id?: Maybe; -export interface NetworkTopNFlowEdges { - node: NetworkTopNFlowItem; + timerange: TimerangeInput; - cursor: CursorType; -} + pagination: PaginationInputPaginated; -export interface NetworkTopNFlowItem { - _id?: Maybe; + sort: HostsSortField; - source?: Maybe; + filterQuery?: Maybe; - destination?: Maybe; + defaultIndex: string[]; - network?: Maybe; + docValueFields: DocValueFieldsInput[]; } +export interface HostOverviewSourceArgs { + id?: Maybe; -export interface TopNFlowItemSource { - autonomous_system?: Maybe; + hostName: string; - domain?: Maybe; + timerange: TimerangeInput; - ip?: Maybe; + defaultIndex: string[]; +} +export interface HostFirstLastSeenSourceArgs { + id?: Maybe; - location?: Maybe; + hostName: string; - flows?: Maybe; + defaultIndex: string[]; - destination_ips?: Maybe; + docValueFields: DocValueFieldsInput[]; } - -export interface AutonomousSystemItem { - name?: Maybe; - - number?: Maybe; +export interface IndicesExistSourceStatusArgs { + defaultIndex: string[]; } - -export interface TopNFlowItemDestination { - autonomous_system?: Maybe; - - domain?: Maybe; - - ip?: Maybe; - - location?: Maybe; - - flows?: Maybe; - - source_ips?: Maybe; +export interface IndexFieldsSourceStatusArgs { + defaultIndex: string[]; } +export interface PersistNoteMutationArgs { + noteId?: Maybe; -export interface NetworkDnsData { - edges: NetworkDnsEdges[]; + version?: Maybe; - totalCount: number; + note: NoteInput; +} +export interface DeleteNoteMutationArgs { + id: string[]; +} +export interface DeleteNoteByTimelineIdMutationArgs { + timelineId: string; - pageInfo: PageInfoPaginated; + version?: Maybe; +} +export interface PersistPinnedEventOnTimelineMutationArgs { + pinnedEventId?: Maybe; - inspect?: Maybe; + eventId: string; - histogram?: Maybe; + timelineId?: Maybe; +} +export interface DeletePinnedEventOnTimelineMutationArgs { + id: string[]; } +export interface DeleteAllPinnedEventsOnTimelineMutationArgs { + timelineId: string; +} +export interface PersistTimelineMutationArgs { + id?: Maybe; -export interface NetworkDnsEdges { - node: NetworkDnsItem; + version?: Maybe; - cursor: CursorType; + timeline: TimelineInput; +} +export interface PersistFavoriteMutationArgs { + timelineId?: Maybe; +} +export interface DeleteTimelineMutationArgs { + id: string[]; } -export interface NetworkDnsItem { - _id?: Maybe; +// ==================================================== +// Documents +// ==================================================== - dnsBytesIn?: Maybe; +export namespace GetHostOverviewQuery { + export type Variables = { + sourceId: string; + hostName: string; + timerange: TimerangeInput; + defaultIndex: string[]; + inspect: boolean; + }; - dnsBytesOut?: Maybe; + export type Query = { + __typename?: 'Query'; - dnsName?: Maybe; + source: Source; + }; - queryCount?: Maybe; + export type Source = { + __typename?: 'Source'; - uniqueDomains?: Maybe; -} + id: string; -export interface MatrixOverOrdinalHistogramData { - x: string; + HostOverview: HostOverview; + }; - y: number; + export type HostOverview = { + __typename?: 'HostItem'; - g: string; -} + _id: Maybe; -export interface NetworkDsOverTimeData { - inspect?: Maybe; + host: Maybe; - matrixHistogramData: MatrixOverTimeHistogramData[]; + cloud: Maybe; - totalCount: number; -} + inspect: Maybe; -export interface NetworkHttpData { - edges: NetworkHttpEdges[]; + endpoint: Maybe; + }; - totalCount: number; + export type Host = { + __typename?: 'HostEcsFields'; - pageInfo: PageInfoPaginated; + architecture: Maybe; - inspect?: Maybe; -} - -export interface NetworkHttpEdges { - node: NetworkHttpItem; - - cursor: CursorType; -} - -export interface NetworkHttpItem { - _id?: Maybe; - - domains: string[]; - - lastHost?: Maybe; - - lastSourceIp?: Maybe; - - methods: string[]; - - path?: Maybe; - - requestCount?: Maybe; - - statuses: string[]; -} - -export interface OverviewNetworkData { - auditbeatSocket?: Maybe; - - filebeatCisco?: Maybe; - - filebeatNetflow?: Maybe; - - filebeatPanw?: Maybe; - - filebeatSuricata?: Maybe; - - filebeatZeek?: Maybe; - - packetbeatDNS?: Maybe; - - packetbeatFlow?: Maybe; - - packetbeatTLS?: Maybe; - - inspect?: Maybe; -} - -export interface OverviewHostData { - auditbeatAuditd?: Maybe; - - auditbeatFIM?: Maybe; - - auditbeatLogin?: Maybe; - - auditbeatPackage?: Maybe; - - auditbeatProcess?: Maybe; - - auditbeatUser?: Maybe; - - endgameDns?: Maybe; - - endgameFile?: Maybe; - - endgameImageLoad?: Maybe; - - endgameNetwork?: Maybe; - - endgameProcess?: Maybe; - - endgameRegistry?: Maybe; - - endgameSecurity?: Maybe; - - filebeatSystemModule?: Maybe; - - winlogbeatSecurity?: Maybe; - - winlogbeatMWSysmonOperational?: Maybe; - - inspect?: Maybe; -} - -export interface TlsData { - edges: TlsEdges[]; - - totalCount: number; - - pageInfo: PageInfoPaginated; - - inspect?: Maybe; -} - -export interface TlsEdges { - node: TlsNode; - - cursor: CursorType; -} - -export interface TlsNode { - _id?: Maybe; - - timestamp?: Maybe; - - notAfter?: Maybe; - - subjects?: Maybe; - - ja3?: Maybe; - - issuers?: Maybe; -} - -export interface UncommonProcessesData { - edges: UncommonProcessesEdges[]; - - totalCount: number; - - pageInfo: PageInfoPaginated; - - inspect?: Maybe; -} - -export interface UncommonProcessesEdges { - node: UncommonProcessItem; - - cursor: CursorType; -} - -export interface UncommonProcessItem { - _id: string; - - instances: number; - - process: ProcessEcsFields; - - hosts: HostEcsFields[]; - - user?: Maybe; -} - -export interface SayMyName { - /** The id of the source */ - appName: string; -} - -export interface TimelineResult { - columns?: Maybe; - - created?: Maybe; - - createdBy?: Maybe; - - dataProviders?: Maybe; - - dateRange?: Maybe; - - description?: Maybe; - - eventIdToNoteIds?: Maybe; - - eventType?: Maybe; - - excludedRowRendererIds?: Maybe; - - favorite?: Maybe; - - filters?: Maybe; - - kqlMode?: Maybe; - - kqlQuery?: Maybe; - - notes?: Maybe; - - noteIds?: Maybe; - - pinnedEventIds?: Maybe; - - pinnedEventsSaveObject?: Maybe; - - savedQueryId?: Maybe; - - savedObjectId: string; - - sort?: Maybe; - - status?: Maybe; - - title?: Maybe; - - templateTimelineId?: Maybe; - - templateTimelineVersion?: Maybe; - - timelineType?: Maybe; - - updated?: Maybe; - - updatedBy?: Maybe; - - version: string; -} - -export interface ColumnHeaderResult { - aggregatable?: Maybe; - - category?: Maybe; - - columnHeaderType?: Maybe; - - description?: Maybe; - - example?: Maybe; - - indexes?: Maybe; - - id?: Maybe; - - name?: Maybe; - - placeholder?: Maybe; - - searchable?: Maybe; - - type?: Maybe; -} - -export interface DataProviderResult { - id?: Maybe; - - name?: Maybe; - - enabled?: Maybe; - - excluded?: Maybe; - - kqlQuery?: Maybe; - - queryMatch?: Maybe; - - type?: Maybe; - - and?: Maybe; -} - -export interface QueryMatchResult { - field?: Maybe; - - displayField?: Maybe; - - value?: Maybe; - - displayValue?: Maybe; - - operator?: Maybe; -} - -export interface DateRangePickerResult { - start?: Maybe; - - end?: Maybe; -} - -export interface FavoriteTimelineResult { - fullName?: Maybe; - - userName?: Maybe; - - favoriteDate?: Maybe; -} - -export interface FilterTimelineResult { - exists?: Maybe; - - meta?: Maybe; - - match_all?: Maybe; - - missing?: Maybe; - - query?: Maybe; - - range?: Maybe; - - script?: Maybe; -} - -export interface FilterMetaTimelineResult { - alias?: Maybe; - - controlledBy?: Maybe; - - disabled?: Maybe; - - field?: Maybe; - - formattedValue?: Maybe; - - index?: Maybe; - - key?: Maybe; - - negate?: Maybe; - - params?: Maybe; - - type?: Maybe; - - value?: Maybe; -} - -export interface SerializedFilterQueryResult { - filterQuery?: Maybe; -} - -export interface SerializedKueryQueryResult { - kuery?: Maybe; - - serializedQuery?: Maybe; -} - -export interface KueryFilterQueryResult { - kind?: Maybe; - - expression?: Maybe; -} - -export interface SortTimelineResult { - columnId?: Maybe; - - sortDirection?: Maybe; -} - -export interface ResponseTimelines { - timeline: (Maybe)[]; - - totalCount?: Maybe; - - defaultTimelineCount?: Maybe; - - templateTimelineCount?: Maybe; - - elasticTemplateTimelineCount?: Maybe; - - customTemplateTimelineCount?: Maybe; - - favoriteCount?: Maybe; -} - -export interface Mutation { - /** Persists a note */ - persistNote: ResponseNote; - - deleteNote?: Maybe; - - deleteNoteByTimelineId?: Maybe; - /** Persists a pinned event in a timeline */ - persistPinnedEventOnTimeline?: Maybe; - /** Remove a pinned events in a timeline */ - deletePinnedEventOnTimeline: boolean; - /** Remove all pinned events in a timeline */ - deleteAllPinnedEventsOnTimeline: boolean; - /** Persists a timeline */ - persistTimeline: ResponseTimeline; - - persistFavorite: ResponseFavoriteTimeline; - - deleteTimeline: boolean; -} - -export interface ResponseNote { - code?: Maybe; - - message?: Maybe; - - note: NoteResult; -} - -export interface ResponseTimeline { - code?: Maybe; - - message?: Maybe; - - timeline: TimelineResult; -} - -export interface ResponseFavoriteTimeline { - code?: Maybe; - - message?: Maybe; - - savedObjectId: string; - - version: string; - - favorite?: Maybe; -} - -export interface EcsEdges { - node: Ecs; - - cursor: CursorType; -} - -export interface EventsTimelineData { - edges: EcsEdges[]; - - totalCount: number; - - pageInfo: PageInfo; - - inspect?: Maybe; -} - -export interface OsFields { - platform?: Maybe; - - name?: Maybe; - - full?: Maybe; - - family?: Maybe; - - version?: Maybe; - - kernel?: Maybe; -} - -export interface HostFields { - architecture?: Maybe; - - id?: Maybe; - - ip?: Maybe<(Maybe)[]>; - - mac?: Maybe<(Maybe)[]>; - - name?: Maybe; - - os?: Maybe; - - type?: Maybe; -} - -// ==================================================== -// Arguments -// ==================================================== - -export interface GetNoteQueryArgs { - id: string; -} -export interface GetNotesByTimelineIdQueryArgs { - timelineId: string; -} -export interface GetNotesByEventIdQueryArgs { - eventId: string; -} -export interface GetAllNotesQueryArgs { - pageInfo?: Maybe; - - search?: Maybe; - - sort?: Maybe; -} -export interface GetAllPinnedEventsByTimelineIdQueryArgs { - timelineId: string; -} -export interface SourceQueryArgs { - /** The id of the source */ - id: string; -} -export interface GetOneTimelineQueryArgs { - id: string; -} -export interface GetAllTimelineQueryArgs { - pageInfo: PageInfoTimeline; - - search?: Maybe; - - sort?: Maybe; - - onlyUserFavorite?: Maybe; - - timelineType?: Maybe; - - status?: Maybe; -} -export interface AuthenticationsSourceArgs { - timerange: TimerangeInput; - - pagination: PaginationInputPaginated; - - filterQuery?: Maybe; - - defaultIndex: string[]; - - docValueFields: DocValueFieldsInput[]; -} -export interface TimelineSourceArgs { - pagination: PaginationInput; - - sortField: SortField; - - fieldRequested: string[]; - - timerange?: Maybe; - - filterQuery?: Maybe; - - defaultIndex: string[]; - - docValueFields: DocValueFieldsInput[]; -} -export interface TimelineDetailsSourceArgs { - eventId: string; - - indexName: string; - - defaultIndex: string[]; - - docValueFields: DocValueFieldsInput[]; -} -export interface LastEventTimeSourceArgs { - id?: Maybe; - - indexKey: LastEventIndexKey; - - details: LastTimeDetails; - - defaultIndex: string[]; - - docValueFields: DocValueFieldsInput[]; -} -export interface HostsSourceArgs { - id?: Maybe; - - timerange: TimerangeInput; - - pagination: PaginationInputPaginated; - - sort: HostsSortField; - - filterQuery?: Maybe; - - defaultIndex: string[]; - - docValueFields: DocValueFieldsInput[]; -} -export interface HostOverviewSourceArgs { - id?: Maybe; - - hostName: string; - - timerange: TimerangeInput; - - defaultIndex: string[]; -} -export interface HostFirstLastSeenSourceArgs { - id?: Maybe; - - hostName: string; - - defaultIndex: string[]; - - docValueFields: DocValueFieldsInput[]; -} -export interface IpOverviewSourceArgs { - id?: Maybe; - - filterQuery?: Maybe; - - ip: string; - - defaultIndex: string[]; - - docValueFields: DocValueFieldsInput[]; -} -export interface UsersSourceArgs { - filterQuery?: Maybe; - - id?: Maybe; - - ip: string; - - pagination: PaginationInputPaginated; - - sort: UsersSortField; - - flowTarget: FlowTarget; - - timerange: TimerangeInput; - - defaultIndex: string[]; -} -export interface KpiNetworkSourceArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} -export interface KpiHostsSourceArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} -export interface KpiHostDetailsSourceArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} -export interface MatrixHistogramSourceArgs { - filterQuery?: Maybe; - - defaultIndex: string[]; - - timerange: TimerangeInput; - - stackByField: string; - - histogramType: HistogramType; -} -export interface NetworkTopCountriesSourceArgs { - id?: Maybe; - - filterQuery?: Maybe; - - ip?: Maybe; - - flowTarget: FlowTargetSourceDest; - - pagination: PaginationInputPaginated; - - sort: NetworkTopTablesSortField; - - timerange: TimerangeInput; - - defaultIndex: string[]; -} -export interface NetworkTopNFlowSourceArgs { - id?: Maybe; - - filterQuery?: Maybe; - - ip?: Maybe; - - flowTarget: FlowTargetSourceDest; - - pagination: PaginationInputPaginated; - - sort: NetworkTopTablesSortField; - - timerange: TimerangeInput; - - defaultIndex: string[]; -} -export interface NetworkDnsSourceArgs { - filterQuery?: Maybe; - - id?: Maybe; - - isPtrIncluded: boolean; - - pagination: PaginationInputPaginated; - - sort: NetworkDnsSortField; - - stackByField?: Maybe; - - timerange: TimerangeInput; - - defaultIndex: string[]; -} -export interface NetworkDnsHistogramSourceArgs { - filterQuery?: Maybe; - - defaultIndex: string[]; - - timerange: TimerangeInput; - - stackByField?: Maybe; - - docValueFields: DocValueFieldsInput[]; -} -export interface NetworkHttpSourceArgs { - id?: Maybe; - - filterQuery?: Maybe; - - ip?: Maybe; - - pagination: PaginationInputPaginated; - - sort: NetworkHttpSortField; - - timerange: TimerangeInput; - - defaultIndex: string[]; -} -export interface OverviewNetworkSourceArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} -export interface OverviewHostSourceArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} -export interface TlsSourceArgs { - filterQuery?: Maybe; - - id?: Maybe; - - ip: string; - - pagination: PaginationInputPaginated; - - sort: TlsSortField; - - flowTarget: FlowTargetSourceDest; - - timerange: TimerangeInput; - - defaultIndex: string[]; -} -export interface UncommonProcessesSourceArgs { - timerange: TimerangeInput; - - pagination: PaginationInputPaginated; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} -export interface IndicesExistSourceStatusArgs { - defaultIndex: string[]; -} -export interface IndexFieldsSourceStatusArgs { - defaultIndex: string[]; -} -export interface PersistNoteMutationArgs { - noteId?: Maybe; - - version?: Maybe; - - note: NoteInput; -} -export interface DeleteNoteMutationArgs { - id: string[]; -} -export interface DeleteNoteByTimelineIdMutationArgs { - timelineId: string; - - version?: Maybe; -} -export interface PersistPinnedEventOnTimelineMutationArgs { - pinnedEventId?: Maybe; - - eventId: string; - - timelineId?: Maybe; -} -export interface DeletePinnedEventOnTimelineMutationArgs { - id: string[]; -} -export interface DeleteAllPinnedEventsOnTimelineMutationArgs { - timelineId: string; -} -export interface PersistTimelineMutationArgs { - id?: Maybe; - - version?: Maybe; - - timeline: TimelineInput; -} -export interface PersistFavoriteMutationArgs { - timelineId?: Maybe; -} -export interface DeleteTimelineMutationArgs { - id: string[]; -} - -// ==================================================== -// Documents -// ==================================================== - -export namespace GetLastEventTimeQuery { - export type Variables = { - sourceId: string; - indexKey: LastEventIndexKey; - details: LastTimeDetails; - defaultIndex: string[]; - docValueFields: DocValueFieldsInput[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - LastEventTime: LastEventTime; - }; - - export type LastEventTime = { - __typename?: 'LastEventTimeData'; - - lastSeen: Maybe; - }; -} - -export namespace GetMatrixHistogramQuery { - export type Variables = { - defaultIndex: string[]; - filterQuery?: Maybe; - histogramType: HistogramType; - inspect: boolean; - sourceId: string; - stackByField: string; - timerange: TimerangeInput; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - MatrixHistogram: MatrixHistogram; - }; - - export type MatrixHistogram = { - __typename?: 'MatrixHistogramOverTimeData'; - - matrixHistogramData: MatrixHistogramData[]; - - totalCount: number; - - inspect: Maybe; - }; - - export type MatrixHistogramData = { - __typename?: 'MatrixOverTimeHistogramData'; - - x: Maybe; - - y: Maybe; - - g: Maybe; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace SourceQuery { - export type Variables = { - sourceId?: Maybe; - defaultIndex: string[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - status: Status; - }; - - export type Status = { - __typename?: 'SourceStatus'; - - indicesExist: boolean; - - indexFields: IndexFields[]; - }; - - export type IndexFields = { - __typename?: 'IndexField'; - - category: string; - - description: Maybe; - - example: Maybe; - - indexes: (Maybe)[]; - - name: string; - - searchable: boolean; - - type: string; - - aggregatable: boolean; - - format: Maybe; - - esTypes: Maybe; - - subType: Maybe; - }; -} - -export namespace GetAuthenticationsQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - pagination: PaginationInputPaginated; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - docValueFields: DocValueFieldsInput[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - Authentications: Authentications; - }; - - export type Authentications = { - __typename?: 'AuthenticationsData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'AuthenticationsEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'AuthenticationItem'; - - _id: string; - - failures: number; - - successes: number; - - user: User; - - lastSuccess: Maybe; - - lastFailure: Maybe; - }; - - export type User = { - __typename?: 'UserEcsFields'; - - name: Maybe; - }; - - export type LastSuccess = { - __typename?: 'LastSourceHost'; - - timestamp: Maybe; - - source: Maybe<_Source>; - - host: Maybe; - }; - - export type _Source = { - __typename?: 'SourceEcsFields'; - - ip: Maybe; - }; - - export type Host = { - __typename?: 'HostEcsFields'; - - id: Maybe; - - name: Maybe; - }; - - export type LastFailure = { - __typename?: 'LastSourceHost'; - - timestamp: Maybe; - - source: Maybe<__Source>; - - host: Maybe<_Host>; - }; - - export type __Source = { - __typename?: 'SourceEcsFields'; - - ip: Maybe; - }; - - export type _Host = { - __typename?: 'HostEcsFields'; - - id: Maybe; - - name: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetHostFirstLastSeenQuery { - export type Variables = { - sourceId: string; - hostName: string; - defaultIndex: string[]; - docValueFields: DocValueFieldsInput[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - HostFirstLastSeen: HostFirstLastSeen; - }; - - export type HostFirstLastSeen = { - __typename?: 'FirstLastSeenHost'; - - firstSeen: Maybe; - - lastSeen: Maybe; - }; -} - -export namespace GetHostsTableQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - pagination: PaginationInputPaginated; - sort: HostsSortField; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - docValueFields: DocValueFieldsInput[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - Hosts: Hosts; - }; - - export type Hosts = { - __typename?: 'HostsData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'HostsEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'HostItem'; - - _id: Maybe; - - lastSeen: Maybe; - - host: Maybe; - }; - - export type Host = { - __typename?: 'HostEcsFields'; - - id: Maybe; - - name: Maybe; - - os: Maybe; - }; - - export type Os = { - __typename?: 'OsEcsFields'; - - name: Maybe; - - version: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetHostOverviewQuery { - export type Variables = { - sourceId: string; - hostName: string; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - HostOverview: HostOverview; - }; - - export type HostOverview = { - __typename?: 'HostItem'; - - _id: Maybe; - - host: Maybe; - - cloud: Maybe; - - inspect: Maybe; - - endpoint: Maybe; - }; - - export type Host = { - __typename?: 'HostEcsFields'; - - architecture: Maybe; - - id: Maybe; - - ip: Maybe; - - mac: Maybe; - - name: Maybe; - - os: Maybe; - - type: Maybe; - }; - - export type Os = { - __typename?: 'OsEcsFields'; - - family: Maybe; - - name: Maybe; - - platform: Maybe; - - version: Maybe; - }; - - export type Cloud = { - __typename?: 'CloudFields'; - - instance: Maybe; - - machine: Maybe; - - provider: Maybe<(Maybe)[]>; - - region: Maybe<(Maybe)[]>; - }; - - export type Instance = { - __typename?: 'CloudInstance'; - - id: Maybe<(Maybe)[]>; - }; - - export type Machine = { - __typename?: 'CloudMachine'; - - type: Maybe<(Maybe)[]>; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; - - export type Endpoint = { - __typename?: 'EndpointFields'; - - endpointPolicy: Maybe; - - policyStatus: Maybe; - - sensorVersion: Maybe; - }; -} - -export namespace GetKpiHostDetailsQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - KpiHostDetails: KpiHostDetails; - }; - - export type KpiHostDetails = { - __typename?: 'KpiHostDetailsData'; - - authSuccess: Maybe; - - authSuccessHistogram: Maybe; - - authFailure: Maybe; - - authFailureHistogram: Maybe; - - uniqueSourceIps: Maybe; - - uniqueSourceIpsHistogram: Maybe; - - uniqueDestinationIps: Maybe; - - uniqueDestinationIpsHistogram: Maybe; - - inspect: Maybe; - }; - - export type AuthSuccessHistogram = KpiHostDetailsChartFields.Fragment; - - export type AuthFailureHistogram = KpiHostDetailsChartFields.Fragment; - - export type UniqueSourceIpsHistogram = KpiHostDetailsChartFields.Fragment; - - export type UniqueDestinationIpsHistogram = KpiHostDetailsChartFields.Fragment; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetKpiHostsQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - KpiHosts: KpiHosts; - }; - - export type KpiHosts = { - __typename?: 'KpiHostsData'; - - hosts: Maybe; - - hostsHistogram: Maybe; - - authSuccess: Maybe; - - authSuccessHistogram: Maybe; - - authFailure: Maybe; - - authFailureHistogram: Maybe; - - uniqueSourceIps: Maybe; - - uniqueSourceIpsHistogram: Maybe; - - uniqueDestinationIps: Maybe; - - uniqueDestinationIpsHistogram: Maybe; - - inspect: Maybe; - }; - - export type HostsHistogram = KpiHostChartFields.Fragment; - - export type AuthSuccessHistogram = KpiHostChartFields.Fragment; - - export type AuthFailureHistogram = KpiHostChartFields.Fragment; - - export type UniqueSourceIpsHistogram = KpiHostChartFields.Fragment; - - export type UniqueDestinationIpsHistogram = KpiHostChartFields.Fragment; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetUncommonProcessesQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - pagination: PaginationInputPaginated; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - UncommonProcesses: UncommonProcesses; - }; - - export type UncommonProcesses = { - __typename?: 'UncommonProcessesData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'UncommonProcessesEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'UncommonProcessItem'; - - _id: string; - - instances: number; - - process: Process; - - user: Maybe; - - hosts: Hosts[]; - }; - - export type Process = { - __typename?: 'ProcessEcsFields'; - - args: Maybe; - - name: Maybe; - }; - - export type User = { - __typename?: 'UserEcsFields'; - - id: Maybe; - - name: Maybe; - }; - - export type Hosts = { - __typename?: 'HostEcsFields'; - - name: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetIpOverviewQuery { - export type Variables = { - sourceId: string; - filterQuery?: Maybe; - ip: string; - defaultIndex: string[]; - inspect: boolean; - docValueFields: DocValueFieldsInput[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - IpOverview: Maybe; - }; - - export type IpOverview = { - __typename?: 'IpOverviewData'; - - source: Maybe<_Source>; - - destination: Maybe; - - host: Host; - - inspect: Maybe; - }; - - export type _Source = { - __typename?: 'Overview'; - - firstSeen: Maybe; - - lastSeen: Maybe; - - autonomousSystem: AutonomousSystem; - - geo: Geo; - }; - - export type AutonomousSystem = { - __typename?: 'AutonomousSystem'; - - number: Maybe; - - organization: Maybe; - }; - - export type Organization = { - __typename?: 'AutonomousSystemOrganization'; - - name: Maybe; - }; - - export type Geo = { - __typename?: 'GeoEcsFields'; - - continent_name: Maybe; - - city_name: Maybe; - - country_iso_code: Maybe; - - country_name: Maybe; - - location: Maybe; - - region_iso_code: Maybe; - - region_name: Maybe; - }; - - export type Location = { - __typename?: 'Location'; - - lat: Maybe; - - lon: Maybe; - }; - - export type Destination = { - __typename?: 'Overview'; - - firstSeen: Maybe; - - lastSeen: Maybe; - - autonomousSystem: _AutonomousSystem; - - geo: _Geo; - }; - - export type _AutonomousSystem = { - __typename?: 'AutonomousSystem'; - - number: Maybe; - - organization: Maybe<_Organization>; - }; - - export type _Organization = { - __typename?: 'AutonomousSystemOrganization'; - - name: Maybe; - }; - - export type _Geo = { - __typename?: 'GeoEcsFields'; - - continent_name: Maybe; - - city_name: Maybe; - - country_iso_code: Maybe; - - country_name: Maybe; - - location: Maybe<_Location>; - - region_iso_code: Maybe; - - region_name: Maybe; - }; - - export type _Location = { - __typename?: 'Location'; - - lat: Maybe; - - lon: Maybe; - }; - - export type Host = { - __typename?: 'HostEcsFields'; - - architecture: Maybe; - - id: Maybe; - - ip: Maybe; - - mac: Maybe; - - name: Maybe; - - os: Maybe; - - type: Maybe; - }; - - export type Os = { - __typename?: 'OsEcsFields'; - - family: Maybe; - - name: Maybe; - - platform: Maybe; - - version: Maybe; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetKpiNetworkQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - KpiNetwork: Maybe; - }; - - export type KpiNetwork = { - __typename?: 'KpiNetworkData'; - - networkEvents: Maybe; - - uniqueFlowId: Maybe; - - uniqueSourcePrivateIps: Maybe; - - uniqueSourcePrivateIpsHistogram: Maybe; - - uniqueDestinationPrivateIps: Maybe; - - uniqueDestinationPrivateIpsHistogram: Maybe; - - dnsQueries: Maybe; - - tlsHandshakes: Maybe; - - inspect: Maybe; - }; - - export type UniqueSourcePrivateIpsHistogram = KpiNetworkChartFields.Fragment; - - export type UniqueDestinationPrivateIpsHistogram = KpiNetworkChartFields.Fragment; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetNetworkDnsQuery { - export type Variables = { - defaultIndex: string[]; - filterQuery?: Maybe; - inspect: boolean; - isPtrIncluded: boolean; - pagination: PaginationInputPaginated; - sort: NetworkDnsSortField; - sourceId: string; - stackByField?: Maybe; - timerange: TimerangeInput; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - NetworkDns: NetworkDns; - }; - - export type NetworkDns = { - __typename?: 'NetworkDnsData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'NetworkDnsEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'NetworkDnsItem'; - - _id: Maybe; - - dnsBytesIn: Maybe; - - dnsBytesOut: Maybe; - - dnsName: Maybe; - - queryCount: Maybe; - - uniqueDomains: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetNetworkHttpQuery { - export type Variables = { - sourceId: string; - ip?: Maybe; - filterQuery?: Maybe; - pagination: PaginationInputPaginated; - sort: NetworkHttpSortField; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - NetworkHttp: NetworkHttp; - }; - - export type NetworkHttp = { - __typename?: 'NetworkHttpData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'NetworkHttpEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'NetworkHttpItem'; - - domains: string[]; - - lastHost: Maybe; - - lastSourceIp: Maybe; - - methods: string[]; - - path: Maybe; - - requestCount: Maybe; - - statuses: string[]; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetNetworkTopCountriesQuery { - export type Variables = { - sourceId: string; - ip?: Maybe; - filterQuery?: Maybe; - pagination: PaginationInputPaginated; - sort: NetworkTopTablesSortField; - flowTarget: FlowTargetSourceDest; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - NetworkTopCountries: NetworkTopCountries; - }; - - export type NetworkTopCountries = { - __typename?: 'NetworkTopCountriesData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'NetworkTopCountriesEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'NetworkTopCountriesItem'; - - source: Maybe<_Source>; - - destination: Maybe; - - network: Maybe; - }; - - export type _Source = { - __typename?: 'TopCountriesItemSource'; - - country: Maybe; - - destination_ips: Maybe; - - flows: Maybe; - - source_ips: Maybe; - }; - - export type Destination = { - __typename?: 'TopCountriesItemDestination'; - - country: Maybe; - - destination_ips: Maybe; - - flows: Maybe; - - source_ips: Maybe; - }; - - export type Network = { - __typename?: 'TopNetworkTablesEcsField'; - - bytes_in: Maybe; - - bytes_out: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetNetworkTopNFlowQuery { - export type Variables = { - sourceId: string; - ip?: Maybe; - filterQuery?: Maybe; - pagination: PaginationInputPaginated; - sort: NetworkTopTablesSortField; - flowTarget: FlowTargetSourceDest; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - NetworkTopNFlow: NetworkTopNFlow; - }; - - export type NetworkTopNFlow = { - __typename?: 'NetworkTopNFlowData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'NetworkTopNFlowEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'NetworkTopNFlowItem'; - - source: Maybe<_Source>; - - destination: Maybe; - - network: Maybe; - }; - - export type _Source = { - __typename?: 'TopNFlowItemSource'; - - autonomous_system: Maybe; - - domain: Maybe; - - ip: Maybe; - - location: Maybe; - - flows: Maybe; - - destination_ips: Maybe; - }; - - export type AutonomousSystem = { - __typename?: 'AutonomousSystemItem'; - - name: Maybe; - - number: Maybe; - }; - - export type Location = { - __typename?: 'GeoItem'; - - geo: Maybe; - - flowTarget: Maybe; - }; - - export type Geo = { - __typename?: 'GeoEcsFields'; - - continent_name: Maybe; - - country_name: Maybe; - - country_iso_code: Maybe; - - city_name: Maybe; - - region_iso_code: Maybe; - - region_name: Maybe; - }; - - export type Destination = { - __typename?: 'TopNFlowItemDestination'; - - autonomous_system: Maybe<_AutonomousSystem>; - - domain: Maybe; - - ip: Maybe; - - location: Maybe<_Location>; - - flows: Maybe; - - source_ips: Maybe; - }; - - export type _AutonomousSystem = { - __typename?: 'AutonomousSystemItem'; - - name: Maybe; - - number: Maybe; - }; - - export type _Location = { - __typename?: 'GeoItem'; - - geo: Maybe<_Geo>; - - flowTarget: Maybe; - }; - - export type _Geo = { - __typename?: 'GeoEcsFields'; - - continent_name: Maybe; - - country_name: Maybe; - - country_iso_code: Maybe; - - city_name: Maybe; - - region_iso_code: Maybe; - - region_name: Maybe; - }; - - export type Network = { - __typename?: 'TopNetworkTablesEcsField'; - - bytes_in: Maybe; - - bytes_out: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetTlsQuery { - export type Variables = { - sourceId: string; - filterQuery?: Maybe; - flowTarget: FlowTargetSourceDest; - ip: string; - pagination: PaginationInputPaginated; - sort: TlsSortField; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - Tls: Tls; - }; - - export type Tls = { - __typename?: 'TlsData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'TlsEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'TlsNode'; - - _id: Maybe; - - subjects: Maybe; - - ja3: Maybe; - - issuers: Maybe; - - notAfter: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetUsersQuery { - export type Variables = { - sourceId: string; - filterQuery?: Maybe; - flowTarget: FlowTarget; - ip: string; - pagination: PaginationInputPaginated; - sort: UsersSortField; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - Users: Users; - }; - - export type Users = { - __typename?: 'UsersData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'UsersEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'UsersNode'; - - user: Maybe; - }; - - export type User = { - __typename?: 'UsersItem'; - - name: Maybe; - - id: Maybe; - - groupId: Maybe; - - groupName: Maybe; - - count: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetOverviewHostQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - OverviewHost: Maybe; - }; - - export type OverviewHost = { - __typename?: 'OverviewHostData'; - - auditbeatAuditd: Maybe; - - auditbeatFIM: Maybe; - - auditbeatLogin: Maybe; - - auditbeatPackage: Maybe; - - auditbeatProcess: Maybe; - - auditbeatUser: Maybe; - - endgameDns: Maybe; - - endgameFile: Maybe; - - endgameImageLoad: Maybe; - - endgameNetwork: Maybe; - - endgameProcess: Maybe; - - endgameRegistry: Maybe; - - endgameSecurity: Maybe; - - filebeatSystemModule: Maybe; - - winlogbeatSecurity: Maybe; - - winlogbeatMWSysmonOperational: Maybe; - - inspect: Maybe; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetOverviewNetworkQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - OverviewNetwork: Maybe; - }; - - export type OverviewNetwork = { - __typename?: 'OverviewNetworkData'; - - auditbeatSocket: Maybe; - - filebeatCisco: Maybe; - - filebeatNetflow: Maybe; - - filebeatPanw: Maybe; - - filebeatSuricata: Maybe; - - filebeatZeek: Maybe; - - packetbeatDNS: Maybe; - - packetbeatFlow: Maybe; - - packetbeatTLS: Maybe; - - inspect: Maybe; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetAllTimeline { - export type Variables = { - pageInfo: PageInfoTimeline; - search?: Maybe; - sort?: Maybe; - onlyUserFavorite?: Maybe; - timelineType?: Maybe; - status?: Maybe; - }; - - export type Query = { - __typename?: 'Query'; - - getAllTimeline: GetAllTimeline; - }; - - export type GetAllTimeline = { - __typename?: 'ResponseTimelines'; - - totalCount: Maybe; - - defaultTimelineCount: Maybe; - - templateTimelineCount: Maybe; - - elasticTemplateTimelineCount: Maybe; - - customTemplateTimelineCount: Maybe; - - favoriteCount: Maybe; - - timeline: (Maybe)[]; - }; - - export type Timeline = { - __typename?: 'TimelineResult'; - - savedObjectId: string; - - description: Maybe; - - favorite: Maybe; - - eventIdToNoteIds: Maybe; - - excludedRowRendererIds: Maybe; - - notes: Maybe; - - noteIds: Maybe; - - pinnedEventIds: Maybe; - - status: Maybe; - - title: Maybe; - - timelineType: Maybe; - - templateTimelineId: Maybe; - - templateTimelineVersion: Maybe; - - created: Maybe; - - createdBy: Maybe; - - updated: Maybe; - - updatedBy: Maybe; - - version: string; - }; - - export type Favorite = { - __typename?: 'FavoriteTimelineResult'; - - fullName: Maybe; - - userName: Maybe; - - favoriteDate: Maybe; - }; - - export type EventIdToNoteIds = { - __typename?: 'NoteResult'; - - eventId: Maybe; - - note: Maybe; - - timelineId: Maybe; - - noteId: string; - - created: Maybe; - - createdBy: Maybe; - - timelineVersion: Maybe; - - updated: Maybe; - - updatedBy: Maybe; - - version: Maybe; - }; - - export type Notes = { - __typename?: 'NoteResult'; - - eventId: Maybe; - - note: Maybe; - - timelineId: Maybe; - - timelineVersion: Maybe; - - noteId: string; - - created: Maybe; - - createdBy: Maybe; - - updated: Maybe; - - updatedBy: Maybe; - - version: Maybe; - }; -} - -export namespace DeleteTimelineMutation { - export type Variables = { - id: string[]; - }; - - export type Mutation = { - __typename?: 'Mutation'; - - deleteTimeline: boolean; - }; -} - -export namespace GetTimelineDetailsQuery { - export type Variables = { - sourceId: string; - eventId: string; - indexName: string; - defaultIndex: string[]; - docValueFields: DocValueFieldsInput[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - TimelineDetails: TimelineDetails; - }; - - export type TimelineDetails = { - __typename?: 'TimelineDetailsData'; - - data: Maybe; - }; - - export type Data = { - __typename?: 'DetailItem'; - - field: string; - - values: Maybe; - - originalValue: Maybe; - }; -} - -export namespace PersistTimelineFavoriteMutation { - export type Variables = { - timelineId?: Maybe; - }; - - export type Mutation = { - __typename?: 'Mutation'; - - persistFavorite: PersistFavorite; - }; - - export type PersistFavorite = { - __typename?: 'ResponseFavoriteTimeline'; - - savedObjectId: string; - - version: string; - - favorite: Maybe; - }; - - export type Favorite = { - __typename?: 'FavoriteTimelineResult'; - - fullName: Maybe; - - userName: Maybe; - - favoriteDate: Maybe; - }; -} - -export namespace GetTimelineQuery { - export type Variables = { - sourceId: string; - fieldRequested: string[]; - pagination: PaginationInput; - sortField: SortField; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - docValueFields: DocValueFieldsInput[]; - timerange: TimerangeInput; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - Timeline: Timeline; - }; - - export type Timeline = { - __typename?: 'TimelineData'; - - totalCount: number; - - inspect: Maybe; - - pageInfo: PageInfo; - - edges: Edges[]; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; - - export type PageInfo = { - __typename?: 'PageInfo'; - - endCursor: Maybe; - - hasNextPage: Maybe; - }; - - export type EndCursor = { - __typename?: 'CursorType'; - - value: Maybe; - - tiebreaker: Maybe; - }; - - export type Edges = { - __typename?: 'TimelineEdges'; - - node: Node; - }; - - export type Node = { - __typename?: 'TimelineItem'; - - _id: string; - - _index: Maybe; - - data: Data[]; - - ecs: Ecs; - }; - - export type Data = { - __typename?: 'TimelineNonEcsData'; - - field: string; - - value: Maybe; - }; - - export type Ecs = { - __typename?: 'ECS'; - - _id: string; - - _index: Maybe; - - timestamp: Maybe; - - message: Maybe; - - system: Maybe; - - event: Maybe; - - agent: Maybe; - - auditd: Maybe; - - file: Maybe; - - host: Maybe; - - rule: Maybe; - - source: Maybe<_Source>; - - destination: Maybe; - - dns: Maybe; - - endgame: Maybe; - - geo: Maybe<__Geo>; - - signal: Maybe; - - suricata: Maybe; - - network: Maybe; - - http: Maybe; - - tls: Maybe; - - url: Maybe; - - user: Maybe; - - winlog: Maybe; - - process: Maybe; - - zeek: Maybe; - }; - - export type System = { - __typename?: 'SystemEcsField'; - - auth: Maybe; - - audit: Maybe; - }; - - export type Auth = { - __typename?: 'AuthEcsFields'; - - ssh: Maybe; - }; - - export type Ssh = { - __typename?: 'SshEcsFields'; - - signature: Maybe; - - method: Maybe; - }; - - export type Audit = { - __typename?: 'AuditEcsFields'; - - package: Maybe; - }; - - export type Package = { - __typename?: 'PackageEcsFields'; - - arch: Maybe; - - entity_id: Maybe; - - name: Maybe; - - size: Maybe; - - summary: Maybe; - - version: Maybe; - }; - - export type Event = { - __typename?: 'EventEcsFields'; - - action: Maybe; - - category: Maybe; - - code: Maybe; - - created: Maybe; - - dataset: Maybe; - - duration: Maybe; - - end: Maybe; - - hash: Maybe; - - id: Maybe; - - kind: Maybe; - - module: Maybe; - - original: Maybe; - - outcome: Maybe; - - risk_score: Maybe; - - risk_score_norm: Maybe; - - severity: Maybe; - - start: Maybe; - - timezone: Maybe; - - type: Maybe; - }; - - export type Agent = { - __typename?: 'AgentEcsField'; - - type: Maybe; - }; - - export type Auditd = { - __typename?: 'AuditdEcsFields'; - - result: Maybe; - - session: Maybe; - - data: Maybe<_Data>; - - summary: Maybe; - }; - - export type _Data = { - __typename?: 'AuditdData'; - - acct: Maybe; - - terminal: Maybe; - - op: Maybe; - }; - - export type Summary = { - __typename?: 'Summary'; - - actor: Maybe; - - object: Maybe; - - how: Maybe; - - message_type: Maybe; - - sequence: Maybe; - }; - - export type Actor = { - __typename?: 'PrimarySecondary'; - - primary: Maybe; - - secondary: Maybe; - }; - - export type Object = { - __typename?: 'PrimarySecondary'; - - primary: Maybe; - - secondary: Maybe; - - type: Maybe; - }; - - export type File = { - __typename?: 'FileFields'; - - name: Maybe; - - path: Maybe; - - target_path: Maybe; - - extension: Maybe; - - type: Maybe; - - device: Maybe; - - inode: Maybe; - - uid: Maybe; - - owner: Maybe; - - gid: Maybe; - - group: Maybe; - - mode: Maybe; - - size: Maybe; - - mtime: Maybe; - - ctime: Maybe; - }; - - export type Host = { - __typename?: 'HostEcsFields'; - - id: Maybe; - - name: Maybe; - - ip: Maybe; - }; - - export type Rule = { - __typename?: 'RuleEcsField'; - - reference: Maybe; - }; - - export type _Source = { - __typename?: 'SourceEcsFields'; - - bytes: Maybe; - - ip: Maybe; - - packets: Maybe; - - port: Maybe; - - geo: Maybe; - }; - - export type Geo = { - __typename?: 'GeoEcsFields'; - - continent_name: Maybe; - - country_name: Maybe; - - country_iso_code: Maybe; - - city_name: Maybe; - - region_iso_code: Maybe; - - region_name: Maybe; - }; - - export type Destination = { - __typename?: 'DestinationEcsFields'; - - bytes: Maybe; + id: Maybe; ip: Maybe; - packets: Maybe; - - port: Maybe; - - geo: Maybe<_Geo>; - }; - - export type _Geo = { - __typename?: 'GeoEcsFields'; - - continent_name: Maybe; - - country_name: Maybe; - - country_iso_code: Maybe; - - city_name: Maybe; - - region_iso_code: Maybe; - - region_name: Maybe; - }; - - export type Dns = { - __typename?: 'DnsEcsFields'; - - question: Maybe; - - resolved_ip: Maybe; - - response_code: Maybe; - }; - - export type Question = { - __typename?: 'DnsQuestionData'; - - name: Maybe; - - type: Maybe; - }; - - export type Endgame = { - __typename?: 'EndgameEcsFields'; - - exit_code: Maybe; - - file_name: Maybe; - - file_path: Maybe; - - logon_type: Maybe; - - parent_process_name: Maybe; - - pid: Maybe; - - process_name: Maybe; - - subject_domain_name: Maybe; - - subject_logon_id: Maybe; - - subject_user_name: Maybe; - - target_domain_name: Maybe; - - target_logon_id: Maybe; - - target_user_name: Maybe; - }; - - export type __Geo = { - __typename?: 'GeoEcsFields'; - - region_name: Maybe; - - country_iso_code: Maybe; - }; - - export type Signal = { - __typename?: 'SignalField'; - - status: Maybe; - - original_time: Maybe; - - rule: Maybe<_Rule>; - }; - - export type _Rule = { - __typename?: 'RuleField'; - - id: Maybe; - - saved_id: Maybe; + mac: Maybe; - timeline_id: Maybe; + name: Maybe; - timeline_title: Maybe; + os: Maybe; - output_index: Maybe; + type: Maybe; + }; - from: Maybe; + export type Os = { + __typename?: 'OsEcsFields'; - index: Maybe; + family: Maybe; - language: Maybe; + name: Maybe; - query: Maybe; + platform: Maybe; - to: Maybe; + version: Maybe; + }; - filters: Maybe; + export type Cloud = { + __typename?: 'CloudFields'; - note: Maybe; + instance: Maybe; - type: Maybe; + machine: Maybe; - threshold: Maybe; + provider: Maybe<(Maybe)[]>; - exceptions_list: Maybe; + region: Maybe<(Maybe)[]>; }; - export type Suricata = { - __typename?: 'SuricataEcsFields'; + export type Instance = { + __typename?: 'CloudInstance'; - eve: Maybe; + id: Maybe<(Maybe)[]>; }; - export type Eve = { - __typename?: 'SuricataEveData'; - - proto: Maybe; - - flow_id: Maybe; + export type Machine = { + __typename?: 'CloudMachine'; - alert: Maybe; + type: Maybe<(Maybe)[]>; }; - export type Alert = { - __typename?: 'SuricataAlertData'; + export type Inspect = { + __typename?: 'Inspect'; - signature: Maybe; + dsl: string[]; - signature_id: Maybe; + response: string[]; }; - export type Network = { - __typename?: 'NetworkEcsField'; + export type Endpoint = { + __typename?: 'EndpointFields'; - bytes: Maybe; + endpointPolicy: Maybe; - community_id: Maybe; + policyStatus: Maybe; - direction: Maybe; + sensorVersion: Maybe; + }; +} - packets: Maybe; +export namespace GetHostFirstLastSeenQuery { + export type Variables = { + sourceId: string; + hostName: string; + defaultIndex: string[]; + docValueFields: DocValueFieldsInput[]; + }; - protocol: Maybe; + export type Query = { + __typename?: 'Query'; - transport: Maybe; + source: Source; }; - export type Http = { - __typename?: 'HttpEcsFields'; - - version: Maybe; + export type Source = { + __typename?: 'Source'; - request: Maybe; + id: string; - response: Maybe; + HostFirstLastSeen: HostFirstLastSeen; }; - export type Request = { - __typename?: 'HttpRequestData'; - - method: Maybe; + export type HostFirstLastSeen = { + __typename?: 'FirstLastSeenHost'; - body: Maybe; + firstSeen: Maybe; - referrer: Maybe; + lastSeen: Maybe; }; +} - export type Body = { - __typename?: 'HttpBodyData'; +export namespace GetHostsTableQuery { + export type Variables = { + sourceId: string; + timerange: TimerangeInput; + pagination: PaginationInputPaginated; + sort: HostsSortField; + filterQuery?: Maybe; + defaultIndex: string[]; + inspect: boolean; + docValueFields: DocValueFieldsInput[]; + }; - bytes: Maybe; + export type Query = { + __typename?: 'Query'; - content: Maybe; + source: Source; }; - export type Response = { - __typename?: 'HttpResponseData'; + export type Source = { + __typename?: 'Source'; - status_code: Maybe; + id: string; - body: Maybe<_Body>; + Hosts: Hosts; }; - export type _Body = { - __typename?: 'HttpBodyData'; + export type Hosts = { + __typename?: 'HostsData'; + + totalCount: number; - bytes: Maybe; + edges: Edges[]; - content: Maybe; - }; + pageInfo: PageInfo; - export type Tls = { - __typename?: 'TlsEcsFields'; + inspect: Maybe; + }; - client_certificate: Maybe; + export type Edges = { + __typename?: 'HostsEdges'; - fingerprints: Maybe; + node: Node; - server_certificate: Maybe; + cursor: Cursor; }; - export type ClientCertificate = { - __typename?: 'TlsClientCertificateData'; + export type Node = { + __typename?: 'HostItem'; - fingerprint: Maybe; - }; + _id: Maybe; - export type Fingerprint = { - __typename?: 'FingerprintData'; + lastSeen: Maybe; - sha1: Maybe; + host: Maybe; }; - export type Fingerprints = { - __typename?: 'TlsFingerprintsData'; + export type Host = { + __typename?: 'HostEcsFields'; - ja3: Maybe; - }; + id: Maybe; - export type Ja3 = { - __typename?: 'TlsJa3Data'; + name: Maybe; - hash: Maybe; + os: Maybe; }; - export type ServerCertificate = { - __typename?: 'TlsServerCertificateData'; + export type Os = { + __typename?: 'OsEcsFields'; + + name: Maybe; - fingerprint: Maybe<_Fingerprint>; + version: Maybe; }; - export type _Fingerprint = { - __typename?: 'FingerprintData'; + export type Cursor = { + __typename?: 'CursorType'; - sha1: Maybe; + value: Maybe; }; - export type Url = { - __typename?: 'UrlEcsFields'; - - original: Maybe; + export type PageInfo = { + __typename?: 'PageInfoPaginated'; - domain: Maybe; + activePage: number; - username: Maybe; + fakeTotalCount: number; - password: Maybe; + showMorePagesIndicator: boolean; }; - export type User = { - __typename?: 'UserEcsFields'; + export type Inspect = { + __typename?: 'Inspect'; - domain: Maybe; + dsl: string[]; - name: Maybe; + response: string[]; }; +} - export type Winlog = { - __typename?: 'WinlogEcsFields'; - - event_id: Maybe; +export namespace GetAllTimeline { + export type Variables = { + pageInfo: PageInfoTimeline; + search?: Maybe; + sort?: Maybe; + onlyUserFavorite?: Maybe; + timelineType?: Maybe; + status?: Maybe; }; - export type Process = { - __typename?: 'ProcessEcsFields'; + export type Query = { + __typename?: 'Query'; - hash: Maybe; + getAllTimeline: GetAllTimeline; + }; - pid: Maybe; + export type GetAllTimeline = { + __typename?: 'ResponseTimelines'; - name: Maybe; + totalCount: Maybe; - ppid: Maybe; + defaultTimelineCount: Maybe; - args: Maybe; + templateTimelineCount: Maybe; - entity_id: Maybe; + elasticTemplateTimelineCount: Maybe; - executable: Maybe; + customTemplateTimelineCount: Maybe; - title: Maybe; + favoriteCount: Maybe; - working_directory: Maybe; + timeline: (Maybe)[]; }; - export type Hash = { - __typename?: 'ProcessHashData'; - - md5: Maybe; - - sha1: Maybe; - - sha256: Maybe; - }; + export type Timeline = { + __typename?: 'TimelineResult'; - export type Zeek = { - __typename?: 'ZeekEcsFields'; + savedObjectId: string; - session_id: Maybe; + description: Maybe; - connection: Maybe; + favorite: Maybe; - notice: Maybe; + eventIdToNoteIds: Maybe; - dns: Maybe<_Dns>; + excludedRowRendererIds: Maybe; - http: Maybe<_Http>; + notes: Maybe; - files: Maybe; + noteIds: Maybe; - ssl: Maybe; - }; + pinnedEventIds: Maybe; - export type Connection = { - __typename?: 'ZeekConnectionData'; + status: Maybe; - local_resp: Maybe; + title: Maybe; - local_orig: Maybe; + timelineType: Maybe; - missed_bytes: Maybe; + templateTimelineId: Maybe; - state: Maybe; + templateTimelineVersion: Maybe; - history: Maybe; - }; + created: Maybe; - export type Notice = { - __typename?: 'ZeekNoticeData'; + createdBy: Maybe; - suppress_for: Maybe; + updated: Maybe; - msg: Maybe; + updatedBy: Maybe; - note: Maybe; + version: string; + }; - sub: Maybe; + export type Favorite = { + __typename?: 'FavoriteTimelineResult'; - dst: Maybe; + fullName: Maybe; - dropped: Maybe; + userName: Maybe; - peer_descr: Maybe; + favoriteDate: Maybe; }; - export type _Dns = { - __typename?: 'ZeekDnsData'; - - AA: Maybe; + export type EventIdToNoteIds = { + __typename?: 'NoteResult'; - qclass_name: Maybe; + eventId: Maybe; - RD: Maybe; + note: Maybe; - qtype_name: Maybe; + timelineId: Maybe; - rejected: Maybe; + noteId: string; - qtype: Maybe; + created: Maybe; - query: Maybe; + createdBy: Maybe; - trans_id: Maybe; + timelineVersion: Maybe; - qclass: Maybe; + updated: Maybe; - RA: Maybe; + updatedBy: Maybe; - TC: Maybe; + version: Maybe; }; - export type _Http = { - __typename?: 'ZeekHttpData'; - - resp_mime_types: Maybe; - - trans_depth: Maybe; - - status_msg: Maybe; - - resp_fuids: Maybe; + export type Notes = { + __typename?: 'NoteResult'; - tags: Maybe; - }; + eventId: Maybe; - export type Files = { - __typename?: 'ZeekFileData'; + note: Maybe; - session_ids: Maybe; + timelineId: Maybe; - timedout: Maybe; + timelineVersion: Maybe; - local_orig: Maybe; + noteId: string; - tx_host: Maybe; + created: Maybe; - source: Maybe; + createdBy: Maybe; - is_orig: Maybe; + updated: Maybe; - overflow_bytes: Maybe; + updatedBy: Maybe; - sha1: Maybe; + version: Maybe; + }; +} - duration: Maybe; +export namespace DeleteTimelineMutation { + export type Variables = { + id: string[]; + }; - depth: Maybe; + export type Mutation = { + __typename?: 'Mutation'; - analyzers: Maybe; + deleteTimeline: boolean; + }; +} - mime_type: Maybe; +export namespace PersistTimelineFavoriteMutation { + export type Variables = { + timelineId?: Maybe; + }; - rx_host: Maybe; + export type Mutation = { + __typename?: 'Mutation'; - total_bytes: Maybe; + persistFavorite: PersistFavorite; + }; - fuid: Maybe; + export type PersistFavorite = { + __typename?: 'ResponseFavoriteTimeline'; - seen_bytes: Maybe; + savedObjectId: string; - missing_bytes: Maybe; + version: string; - md5: Maybe; + favorite: Maybe; }; - export type Ssl = { - __typename?: 'ZeekSslData'; - - cipher: Maybe; + export type Favorite = { + __typename?: 'FavoriteTimelineResult'; - established: Maybe; + fullName: Maybe; - resumed: Maybe; + userName: Maybe; - version: Maybe; + favoriteDate: Maybe; }; } @@ -5558,6 +2201,8 @@ export namespace GetOneTimeline { kqlQuery: Maybe; + indexNames: Maybe; + notes: Maybe; noteIds: Maybe; @@ -5890,6 +2535,8 @@ export namespace PersistTimelineMutation { kqlQuery: Maybe; + indexNames: Maybe; + title: Maybe; dateRange: Maybe; @@ -6125,33 +2772,3 @@ export namespace PersistTimelinePinnedEventMutation { version: Maybe; }; } - -export namespace KpiHostDetailsChartFields { - export type Fragment = { - __typename?: 'KpiHostHistogramData'; - - x: Maybe; - - y: Maybe; - }; -} - -export namespace KpiHostChartFields { - export type Fragment = { - __typename?: 'KpiHostHistogramData'; - - x: Maybe; - - y: Maybe; - }; -} - -export namespace KpiNetworkChartFields { - export type Fragment = { - __typename?: 'KpiNetworkHistogramData'; - - x: Maybe; - - y: Maybe; - }; -} diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx index 606b43c6508fb..4f64cca45d162 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx @@ -40,7 +40,12 @@ describe('FirstLastSeen Component', () => { useFirstLastSeenHostMock.mockReturnValue([true, MOCKED_RESPONSE]); const { container } = render( - + ); expect(container.innerHTML).toBe( @@ -52,7 +57,12 @@ describe('FirstLastSeen Component', () => { useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]); const { container } = render( - + ); @@ -69,7 +79,12 @@ describe('FirstLastSeen Component', () => { useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]); const { container } = render( - + ); await act(() => @@ -91,7 +106,12 @@ describe('FirstLastSeen Component', () => { ]); const { container } = render( - + ); @@ -114,7 +134,12 @@ describe('FirstLastSeen Component', () => { ]); const { container } = render( - + ); @@ -137,7 +162,12 @@ describe('FirstLastSeen Component', () => { ]); const { container } = render( - + ); await act(() => @@ -157,7 +187,12 @@ describe('FirstLastSeen Component', () => { ]); const { container } = render( - + ); await act(() => diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx index a1b72fb39069c..ee415560cf9de 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx @@ -10,6 +10,7 @@ import React, { useMemo } from 'react'; import { useFirstLastSeenHost } from '../../containers/hosts/first_last_seen'; import { getEmptyTagValue } from '../../../common/components/empty_value'; import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; +import { DocValueFields } from '../../../../common/search_strategy'; export enum FirstLastSeenHostType { FIRST_SEEN = 'first-seen', @@ -17,47 +18,53 @@ export enum FirstLastSeenHostType { } interface FirstLastSeenHostProps { + docValueFields: DocValueFields[]; hostName: string; + indexNames: string[]; type: FirstLastSeenHostType; } -export const FirstLastSeenHost = React.memo(({ hostName, type }) => { - const [loading, { firstSeen, lastSeen, errorMessage }] = useFirstLastSeenHost({ - hostName, - }); - const valueSeen = useMemo( - () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen), - [firstSeen, lastSeen, type] - ); +export const FirstLastSeenHost = React.memo( + ({ docValueFields, hostName, type, indexNames }) => { + const [loading, { firstSeen, lastSeen, errorMessage }] = useFirstLastSeenHost({ + docValueFields, + hostName, + indexNames, + }); + const valueSeen = useMemo( + () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen), + [firstSeen, lastSeen, type] + ); + + if (errorMessage != null) { + return ( + + + + ); + } - if (errorMessage != null) { return ( - - - + <> + {loading && } + {!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date' + ? valueSeen + : !loading && + valueSeen != null && ( + + + + )} + {!loading && valueSeen == null && getEmptyTagValue()} + ); } - - return ( - <> - {loading && } - {!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date' - ? valueSeen - : !loading && - valueSeen != null && ( - - - - )} - {!loading && valueSeen == null && getEmptyTagValue()} - - ); -}); +); FirstLastSeenHost.displayName = 'FirstLastSeenHost'; diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap index 3143e680913b2..1d70f4f72ac8b 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap @@ -68,97 +68,6 @@ exports[`Hosts Table rendering it renders the default Hosts table 1`] = ` } fakeTotalCount={50} id="hostsQuery" - indexPattern={ - Object { - "fields": Array [ - Object { - "aggregatable": true, - "name": "@timestamp", - "searchable": true, - "type": "date", - }, - Object { - "aggregatable": true, - "name": "@version", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.ephemeral_id", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.hostname", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.id", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test1", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test2", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test3", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test4", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test5", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test6", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test7", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test8", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "host.name", - "searchable": true, - "type": "string", - }, - ], - "title": "filebeat-*,auditbeat-*,packetbeat-*", - } - } isInspect={false} loadPage={[MockFunction]} loading={false} diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx index c4a391687843c..29e4dc48ae3c7 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx @@ -12,7 +12,6 @@ import { MockedProvider } from 'react-apollo/test-utils'; import '../../../common/mock/match_media'; import { apolloClientObservable, - mockIndexPattern, mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, @@ -69,7 +68,6 @@ describe('Hosts Table', () => { data={mockData.Hosts.edges} id="hostsQuery" isInspect={false} - indexPattern={mockIndexPattern} fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.Hosts.pageInfo)} loading={false} loadPage={loadPage} @@ -92,7 +90,6 @@ describe('Hosts Table', () => { void; @@ -77,7 +75,6 @@ const HostsTableComponent = React.memo( direction, fakeTotalCount, id, - indexPattern, isInspect, limit, loading, diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx index 0949616827470..84003e5dea5e9 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx @@ -42,6 +42,7 @@ export const fieldsMapping: Readonly = [ const HostsKpiAuthenticationsComponent: React.FC = ({ filterQuery, from, + indexNames, to, narrowDateRange, setQuery, @@ -50,6 +51,7 @@ const HostsKpiAuthenticationsComponent: React.FC = ({ const [loading, { refetch, id, inspect, ...data }] = useHostsKpiAuthentications({ filterQuery, endDate: to, + indexNames, startDate: from, skip, }); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx index b1c4d6331e450..908ff717e2711 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx @@ -31,6 +31,7 @@ export const fieldsMapping: Readonly = [ const HostsKpiHostsComponent: React.FC = ({ filterQuery, from, + indexNames, to, narrowDateRange, setQuery, @@ -39,6 +40,7 @@ const HostsKpiHostsComponent: React.FC = ({ const [loading, { refetch, id, inspect, ...data }] = useHostsKpiHosts({ filterQuery, endDate: to, + indexNames, startDate: from, skip, }); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx index fff4c64900a8b..6174e174db5a6 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx @@ -13,12 +13,13 @@ import { HostsKpiUniqueIps } from './unique_ips'; import { HostsKpiProps } from './types'; export const HostsKpiComponent = React.memo( - ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => ( + ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => ( ( ( ( HostsKpiComponent.displayName = 'HostsKpiComponent'; export const HostsDetailsKpiComponent = React.memo( - ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => ( + ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => ( ( = [ const HostsKpiUniqueIpsComponent: React.FC = ({ filterQuery, from, + indexNames, to, narrowDateRange, setQuery, @@ -50,6 +51,7 @@ const HostsKpiUniqueIpsComponent: React.FC = ({ const [loading, { refetch, id, inspect, ...data }] = useHostsKpiUniqueIps({ filterQuery, endDate: to, + indexNames, startDate: from, skip, }); diff --git a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx index 5ace3439a2de6..41f443f14cafe 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx @@ -30,18 +30,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = shallow( @@ -54,18 +50,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -79,18 +71,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -105,18 +93,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -131,18 +115,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -157,18 +137,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -183,18 +159,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -208,18 +180,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -233,18 +201,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx index 31d7fb10edb1c..c7025bb489ae4 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx @@ -9,7 +9,10 @@ import React, { useCallback, useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { UncommonProcessesEdges, UncommonProcessItem } from '../../../graphql/types'; +import { + HostsUncommonProcessesEdges, + HostsUncommonProcessItem, +} from '../../../../common/search_strategy'; import { State } from '../../../common/store'; import { hostsActions, hostsModel, hostsSelectors } from '../../store'; import { defaultToEmptyTag, getEmptyValue } from '../../../common/components/empty_value'; @@ -21,7 +24,7 @@ import { getRowItemDraggables } from '../../../common/components/tables/helpers' import { HostsType } from '../../store/model'; const tableType = hostsModel.HostsTableType.uncommonProcesses; interface OwnProps { - data: UncommonProcessesEdges[]; + data: HostsUncommonProcessesEdges[]; fakeTotalCount: number; id: string; isInspect: boolean; @@ -33,12 +36,12 @@ interface OwnProps { } export type UncommonProcessTableColumns = [ - Columns, - Columns, - Columns, - Columns, - Columns, - Columns + Columns, + Columns, + Columns, + Columns, + Columns, + Columns ]; type UncommonProcessTableProps = OwnProps & PropsFromRedux; @@ -212,7 +215,7 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [ }, ]; -export const getHostNames = (node: UncommonProcessItem): string[] => { +export const getHostNames = (node: HostsUncommonProcessItem): string[] => { if (node.hosts != null) { return node.hosts .filter((host) => host.name != null && host.name[0] != null) diff --git a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/mock.ts b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/mock.ts index 52b835278634b..56853c1bfaae1 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/mock.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/mock.ts @@ -4,116 +4,115 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UncommonProcessesData } from '../../../graphql/types'; +import { HostsUncommonProcessesStrategyResponse } from '../../../../common/search_strategy'; -export const mockData: { UncommonProcess: UncommonProcessesData } = { - UncommonProcess: { - totalCount: 5, - edges: [ - { - node: { - _id: 'cPsuhGcB0WOhS6qyTKC0', - process: { - title: ['Hello World'], - name: ['elrond.elstc.co'], - }, - hosts: [], - instances: 93, - user: { - id: ['0'], - name: ['root'], - }, +export const mockData: HostsUncommonProcessesStrategyResponse = { + totalCount: 5, + edges: [ + { + node: { + _id: 'cPsuhGcB0WOhS6qyTKC0', + process: { + title: ['Hello World'], + name: ['elrond.elstc.co'], }, - cursor: { - value: '98966fa2013c396155c460d35c0902be', + hosts: [], + instances: 93, + user: { + id: ['0'], + name: ['root'], }, }, - { - node: { - _id: 'cPsuhGcB0WOhS6qyTKC0', - process: { - title: ['Hello World'], - name: ['elrond.elstc.co'], - }, - hosts: [{ id: ['host-id-1'], name: ['hello-world'] }], - instances: 93, - user: { - id: ['0'], - name: ['root'], - }, + cursor: { + value: '98966fa2013c396155c460d35c0902be', + }, + }, + { + node: { + _id: 'cPsuhGcB0WOhS6qyTKC0', + process: { + title: ['Hello World'], + name: ['elrond.elstc.co'], }, - cursor: { - value: '98966fa2013c396155c460d35c0902be', + hosts: [{ id: ['host-id-1'], name: ['hello-world'] }], + instances: 93, + user: { + id: ['0'], + name: ['root'], }, }, - { - node: { - _id: 'KwQDiWcB0WOhS6qyXmrW', - process: { - title: ['Hello World'], - name: ['siem-kibana'], - }, - hosts: [ - { id: ['host-id-1'], name: ['hello-world'] }, - { id: ['host-id-2'], name: ['hello-world-2'] }, - ], - instances: 97, - user: { - id: ['1'], - name: ['Evan'], - }, + cursor: { + value: '98966fa2013c396155c460d35c0902be', + }, + }, + { + node: { + _id: 'KwQDiWcB0WOhS6qyXmrW', + process: { + title: ['Hello World'], + name: ['siem-kibana'], }, - cursor: { - value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + hosts: [ + { id: ['host-id-1'], name: ['hello-world'] }, + { id: ['host-id-2'], name: ['hello-world-2'] }, + ], + instances: 97, + user: { + id: ['1'], + name: ['Evan'], }, }, - { - node: { - _id: 'KwQDiWcB0WOhS6qyXmrW', - process: { - title: ['Hello World'], - name: ['siem-kibana'], - }, - hosts: [{ ip: ['127.0.0.1'] }], - instances: 97, - user: { - id: ['1'], - name: ['Evan'], - }, + cursor: { + value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + }, + }, + { + node: { + _id: 'KwQDiWcB0WOhS6qyXmrW', + process: { + title: ['Hello World'], + name: ['siem-kibana'], }, - cursor: { - value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + hosts: [{ ip: ['127.0.0.1'] }], + instances: 97, + user: { + id: ['1'], + name: ['Evan'], }, }, - { - node: { - _id: 'KwQDiWcB0WOhS6qyXmrW', - process: { - title: ['Hello World'], - name: ['siem-kibana'], - }, - hosts: [ - { ip: ['127.0.0.1'] }, - { id: ['host-id-1'], name: ['hello-world'] }, - { ip: ['127.0.0.1'] }, - { id: ['host-id-2'], name: ['hello-world-2'] }, - { ip: ['127.0.0.1'] }, - ], - instances: 97, - user: { - id: ['1'], - name: ['Evan'], - }, + cursor: { + value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + }, + }, + { + node: { + _id: 'KwQDiWcB0WOhS6qyXmrW', + process: { + title: ['Hello World'], + name: ['siem-kibana'], }, - cursor: { - value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + hosts: [ + { ip: ['127.0.0.1'] }, + { id: ['host-id-1'], name: ['hello-world'] }, + { ip: ['127.0.0.1'] }, + { id: ['host-id-2'], name: ['hello-world-2'] }, + { ip: ['127.0.0.1'] }, + ], + instances: 97, + user: { + id: ['1'], + name: ['Evan'], }, }, - ], - pageInfo: { - activePage: 1, - fakeTotalCount: 50, - showMorePagesIndicator: true, + cursor: { + value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + }, }, + ], + pageInfo: { + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, + rawResponse: {} as HostsUncommonProcessesStrategyResponse['rawResponse'], }; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.gql_query.ts b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.gql_query.ts deleted file mode 100644 index c68816b34c175..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.gql_query.ts +++ /dev/null @@ -1,74 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const authenticationsQuery = gql` - query GetAuthenticationsQuery( - $sourceId: ID! - $timerange: TimerangeInput! - $pagination: PaginationInputPaginated! - $filterQuery: String - $defaultIndex: [String!]! - $inspect: Boolean! - $docValueFields: [docValueFieldsInput!]! - ) { - source(id: $sourceId) { - id - Authentications( - timerange: $timerange - pagination: $pagination - filterQuery: $filterQuery - defaultIndex: $defaultIndex - docValueFields: $docValueFields - ) { - totalCount - edges { - node { - _id - failures - successes - user { - name - } - lastSuccess { - timestamp - source { - ip - } - host { - id - name - } - } - lastFailure { - timestamp - source { - ip - } - host { - id - name - } - } - } - cursor { - value - } - } - pageInfo { - activePage - fakeTotalCount - showMorePagesIndicator - } - inspect @include(if: $inspect) { - dsl - response - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx index 34f2385051f4c..b1563e85c93dd 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx @@ -9,9 +9,12 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { shallowEqual, useSelector } from 'react-redux'; import deepEqual from 'fast-deep-equal'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { HostsQueries } from '../../../../common/search_strategy/security_solution'; import { HostAuthenticationsRequestOptions, @@ -52,6 +55,7 @@ interface UseAuthentications { docValueFields?: DocValueFields[]; filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; startDate: string; type: hostsModel.HostsType; skip: boolean; @@ -61,6 +65,7 @@ export const useAuthentications = ({ docValueFields, filterQuery, endDate, + indexNames, startDate, type, skip, @@ -70,15 +75,14 @@ export const useAuthentications = ({ (state: State) => getAuthenticationsSelector(state, type), shallowEqual ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [authenticationsRequest, setAuthenticationsRequest] = useState< HostAuthenticationsRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], factoryQueryType: HostsQueries.authentications, filterQuery: createFilter(filterQuery), @@ -136,7 +140,7 @@ export const useAuthentications = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setAuthenticationsResponse((prevResponse) => ({ @@ -149,7 +153,7 @@ export const useAuthentications = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -182,7 +186,7 @@ export const useAuthentications = ({ setAuthenticationsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), @@ -197,7 +201,7 @@ export const useAuthentications = ({ } return prevRequest; }); - }, [activePage, defaultIndex, docValueFields, endDate, filterQuery, limit, skip, startDate]); + }, [activePage, docValueFields, endDate, filterQuery, indexNames, limit, skip, startDate]); useEffect(() => { authenticationsSearch(authenticationsRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx index 7b248d867bb76..5b69e20398a35 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx @@ -10,7 +10,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { useKibana } from '../../../../common/lib/kibana'; import { @@ -21,7 +20,11 @@ import { } from '../../../../../common/search_strategy/security_solution/hosts'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; @@ -37,9 +40,10 @@ export interface HostDetailsArgs { } interface UseHostDetails { - id?: string; - hostName: string; endDate: string; + hostName: string; + id?: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -47,17 +51,17 @@ interface UseHostDetails { export const useHostDetails = ({ endDate, hostName, + indexNames, + id = ID, skip = false, startDate, - id = ID, }: UseHostDetails): [boolean, HostDetailsArgs] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [hostDetailsRequest, setHostDetailsRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, hostName, factoryQueryType: HostsQueries.details, timerange: { @@ -93,7 +97,7 @@ export const useHostDetails = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setHostDetailsResponse((prevResponse) => ({ @@ -104,7 +108,7 @@ export const useHostDetails = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -138,7 +142,7 @@ export const useHostDetails = ({ setHostDetailsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, hostName, timerange: { interval: '12h', @@ -151,7 +155,7 @@ export const useHostDetails = ({ } return prevRequest; }); - }, [defaultIndex, endDate, hostName, startDate, skip]); + }, [endDate, hostName, indexNames, startDate, skip]); useEffect(() => { hostDetailsSearch(hostDetailsRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx index 12a82c7980b61..0236270d18618 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx @@ -10,11 +10,9 @@ import { Query } from 'react-apollo'; import { connect } from 'react-redux'; import { compose } from 'redux'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel, inputsSelectors, State } from '../../../../common/store'; import { getDefaultFetchPolicy } from '../../../../common/containers/helpers'; import { QueryTemplate, QueryTemplateProps } from '../../../../common/containers/query_template'; -import { withKibana, WithKibanaProps } from '../../../../common/lib/kibana'; import { HostOverviewQuery } from './host_overview.gql_query'; import { GetHostOverviewQuery, HostItem } from '../../../../graphql/types'; @@ -42,7 +40,7 @@ export interface OwnProps extends QueryTemplateProps { endDate: string; } -type HostsOverViewProps = OwnProps & HostOverviewReduxProps & WithKibanaProps; +type HostsOverViewProps = OwnProps & HostOverviewReduxProps; class HostOverviewByNameComponentQuery extends QueryTemplate< HostsOverViewProps, @@ -52,10 +50,10 @@ class HostOverviewByNameComponentQuery extends QueryTemplate< public render() { const { id = ID, + indexNames, isInspected, children, hostName, - kibana, skip, sourceId, startDate, @@ -75,7 +73,7 @@ class HostOverviewByNameComponentQuery extends QueryTemplate< from: startDate, to: endDate, }, - defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), + defaultIndex: indexNames, inspect: isInspected, }} > @@ -108,6 +106,5 @@ const makeMapStateToProps = () => { }; export const HostOverviewByNameQuery = compose>( - connect(makeMapStateToProps), - withKibana + connect(makeMapStateToProps) )(HostOverviewByNameComponentQuery); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx index 169fe58e9a2cc..cc944a59571f1 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx @@ -7,18 +7,20 @@ import deepEqual from 'fast-deep-equal'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; - import { useKibana } from '../../../../common/lib/kibana'; import { HostsQueries, HostFirstLastSeenRequestOptions, HostFirstLastSeenStrategyResponse, } from '../../../../../common/search_strategy/security_solution'; -import { useWithSource } from '../../../../common/containers/source'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { DocValueFields } from '../../../../../common/search_strategy'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; const ID = 'firstLastSeenHostQuery'; @@ -29,21 +31,23 @@ export interface FirstLastSeenHostArgs { lastSeen?: string | null; } interface UseHostFirstLastSeen { + docValueFields: DocValueFields[]; hostName: string; + indexNames: string[]; } export const useFirstLastSeenHost = ({ + docValueFields, hostName, + indexNames, }: UseHostFirstLastSeen): [boolean, FirstLastSeenHostArgs] => { - const { docValueFields } = useWithSource('default'); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [firstLastSeenHostRequest, setFirstLastSeenHostRequest] = useState< HostFirstLastSeenRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], factoryQueryType: HostsQueries.firstLastSeen, hostName, @@ -72,7 +76,7 @@ export const useFirstLastSeenHost = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setFirstLastSeenHostResponse((prevResponse) => ({ @@ -83,7 +87,7 @@ export const useFirstLastSeenHost = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -120,7 +124,7 @@ export const useFirstLastSeenHost = ({ setFirstLastSeenHostRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], hostName, }; @@ -129,7 +133,7 @@ export const useFirstLastSeenHost = ({ } return prevRequest; }); - }, [defaultIndex, docValueFields, hostName]); + }, [indexNames, docValueFields, hostName]); useEffect(() => { firstLastSeenHostSearch(firstLastSeenHostRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx index 11b853e0ebeb0..6ca0272e58d7d 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx @@ -9,7 +9,6 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, State } from '../../../common/store'; import { createFilter } from '../../../common/containers/helpers'; import { useKibana } from '../../../common/lib/kibana'; @@ -26,7 +25,11 @@ import { import { ESTermQuery } from '../../../../common/typed_json'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; @@ -50,6 +53,7 @@ interface UseAllHost { docValueFields?: DocValueFields[]; filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; type: hostsModel.HostsType; @@ -59,6 +63,7 @@ export const useAllHost = ({ docValueFields, filterQuery, endDate, + indexNames, skip = false, startDate, type, @@ -67,13 +72,12 @@ export const useAllHost = ({ const { activePage, direction, limit, sortField } = useSelector((state: State) => getHostsSelector(state, type) ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [hostsRequest, setHostRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], factoryQueryType: HostsQueries.hosts, filterQuery: createFilter(filterQuery), @@ -133,7 +137,7 @@ export const useAllHost = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setHostsResponse((prevResponse) => ({ @@ -146,7 +150,7 @@ export const useAllHost = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -177,7 +181,7 @@ export const useAllHost = ({ setHostRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), @@ -198,11 +202,11 @@ export const useAllHost = ({ }); }, [ activePage, - defaultIndex, direction, docValueFields, endDate, filterQuery, + indexNames, limit, skip, startDate, diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.gql_query.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.gql_query.tsx deleted file mode 100644 index 077f49c4bdfa6..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.gql_query.tsx +++ /dev/null @@ -1,52 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const kpiHostDetailsQuery = gql` - fragment KpiHostDetailsChartFields on KpiHostHistogramData { - x - y - } - - query GetKpiHostDetailsQuery( - $sourceId: ID! - $timerange: TimerangeInput! - $filterQuery: String - $defaultIndex: [String!]! - $inspect: Boolean! - ) { - source(id: $sourceId) { - id - KpiHostDetails( - timerange: $timerange - filterQuery: $filterQuery - defaultIndex: $defaultIndex - ) { - authSuccess - authSuccessHistogram { - ...KpiHostDetailsChartFields - } - authFailure - authFailureHistogram { - ...KpiHostDetailsChartFields - } - uniqueSourceIps - uniqueSourceIpsHistogram { - ...KpiHostDetailsChartFields - } - uniqueDestinationIps - uniqueDestinationIpsHistogram { - ...KpiHostDetailsChartFields - } - inspect @include(if: $inspect) { - dsl - response - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx deleted file mode 100644 index 1551e7d706714..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx +++ /dev/null @@ -1,85 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getOr } from 'lodash/fp'; -import React from 'react'; -import { Query } from 'react-apollo'; -import { connect, ConnectedProps } from 'react-redux'; - -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { KpiHostDetailsData, GetKpiHostDetailsQuery } from '../../../graphql/types'; -import { inputsModel, inputsSelectors, State } from '../../../common/store'; -import { useUiSetting } from '../../../common/lib/kibana'; -import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; -import { QueryTemplateProps } from '../../../common/containers/query_template'; - -import { kpiHostDetailsQuery } from './index.gql_query'; - -const ID = 'kpiHostDetailsQuery'; - -export interface KpiHostDetailsArgs { - id: string; - inspect: inputsModel.InspectQuery; - kpiHostDetails: KpiHostDetailsData; - loading: boolean; - refetch: inputsModel.Refetch; -} - -export interface QueryKpiHostDetailsProps extends QueryTemplateProps { - children: (args: KpiHostDetailsArgs) => React.ReactNode; -} - -const KpiHostDetailsComponentQuery = React.memo( - ({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => ( - - query={kpiHostDetailsQuery} - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - skip={skip} - variables={{ - sourceId, - timerange: { - interval: '12h', - from: startDate!, - to: endDate!, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), - inspect: isInspected, - }} - > - {({ data, loading, refetch }) => { - const kpiHostDetails = getOr({}, `source.KpiHostDetails`, data); - return children({ - id, - inspect: getOr(null, 'source.KpiHostDetails.inspect', data), - kpiHostDetails, - loading, - refetch, - }); - }} - - ) -); - -KpiHostDetailsComponentQuery.displayName = 'KpiHostDetailsComponentQuery'; - -const makeMapStateToProps = () => { - const getQuery = inputsSelectors.globalQueryByIdSelector(); - const mapStateToProps = (state: State, { id = ID }: QueryKpiHostDetailsProps) => { - const { isInspected } = getQuery(state, id); - return { - isInspected, - }; - }; - return mapStateToProps; -}; - -const connector = connect(makeMapStateToProps); - -type PropsFromRedux = ConnectedProps; - -export const KpiHostDetailsQuery = connector(KpiHostDetailsComponentQuery); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx index 0d90b73e0a584..404231be1e6cd 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx @@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -37,6 +36,7 @@ export interface HostsKpiAuthenticationsArgs interface UseHostsKpiAuthentications { filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -44,18 +44,18 @@ interface UseHostsKpiAuthentications { export const useHostsKpiAuthentications = ({ filterQuery, endDate, + indexNames, skip = false, startDate, }: UseHostsKpiAuthentications): [boolean, HostsKpiAuthenticationsArgs] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [hostsKpiAuthenticationsRequest, setHostsKpiAuthenticationsRequest] = useState< HostsKpiAuthenticationsRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: HostsKpiQueries.kpiAuthentications, filterQuery: createFilter(filterQuery), id: ID, @@ -147,7 +147,7 @@ export const useHostsKpiAuthentications = ({ setHostsKpiAuthenticationsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -160,7 +160,7 @@ export const useHostsKpiAuthentications = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, skip, startDate]); + }, [indexNames, endDate, filterQuery, skip, startDate]); useEffect(() => { hostsKpiAuthenticationsSearch(hostsKpiAuthenticationsRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx index 190ce1aa7eae1..bb918a9214f40 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx @@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -36,6 +35,7 @@ export interface HostsKpiHostsArgs extends Omit { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [hostsKpiHostsRequest, setHostsKpiHostsRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: HostsKpiQueries.kpiHosts, filterQuery: createFilter(filterQuery), id: ID, @@ -135,7 +135,7 @@ export const useHostsKpiHosts = ({ setHostsKpiHostsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -148,7 +148,7 @@ export const useHostsKpiHosts = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, skip, startDate]); + }, [indexNames, endDate, filterQuery, skip, startDate]); useEffect(() => { hostsKpiHostsSearch(hostsKpiHostsRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.gql_query.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.gql_query.ts deleted file mode 100644 index 37d54455db1fd..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.gql_query.ts +++ /dev/null @@ -1,52 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const kpiHostsQuery = gql` - fragment KpiHostChartFields on KpiHostHistogramData { - x - y - } - - query GetKpiHostsQuery( - $sourceId: ID! - $timerange: TimerangeInput! - $filterQuery: String - $defaultIndex: [String!]! - $inspect: Boolean! - ) { - source(id: $sourceId) { - id - KpiHosts(timerange: $timerange, filterQuery: $filterQuery, defaultIndex: $defaultIndex) { - hosts - hostsHistogram { - ...KpiHostChartFields - } - authSuccess - authSuccessHistogram { - ...KpiHostChartFields - } - authFailure - authFailureHistogram { - ...KpiHostChartFields - } - uniqueSourceIps - uniqueSourceIpsHistogram { - ...KpiHostChartFields - } - uniqueDestinationIps - uniqueDestinationIpsHistogram { - ...KpiHostChartFields - } - inspect @include(if: $inspect) { - dsl - response - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx index ac5cc12807f00..b8e93eef8dc91 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx @@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -37,6 +36,7 @@ export interface HostsKpiUniqueIpsArgs interface UseHostsKpiUniqueIps { filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -44,18 +44,18 @@ interface UseHostsKpiUniqueIps { export const useHostsKpiUniqueIps = ({ filterQuery, endDate, + indexNames, skip = false, startDate, }: UseHostsKpiUniqueIps): [boolean, HostsKpiUniqueIpsArgs] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [hostsKpiUniqueIpsRequest, setHostsKpiUniqueIpsRequest] = useState< HostsKpiUniqueIpsRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: HostsKpiQueries.kpiUniqueIps, filterQuery: createFilter(filterQuery), id: ID, @@ -144,7 +144,7 @@ export const useHostsKpiUniqueIps = ({ setHostsKpiUniqueIpsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -157,7 +157,7 @@ export const useHostsKpiUniqueIps = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, skip, startDate]); + }, [indexNames, endDate, filterQuery, skip, startDate]); useEffect(() => { hostsKpiUniqueIpsSearch(hostsKpiUniqueIpsRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.gql_query.ts b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.gql_query.ts deleted file mode 100644 index d984de020faa1..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.gql_query.ts +++ /dev/null @@ -1,59 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const uncommonProcessesQuery = gql` - query GetUncommonProcessesQuery( - $sourceId: ID! - $timerange: TimerangeInput! - $pagination: PaginationInputPaginated! - $filterQuery: String - $defaultIndex: [String!]! - $inspect: Boolean! - ) { - source(id: $sourceId) { - id - UncommonProcesses( - timerange: $timerange - pagination: $pagination - filterQuery: $filterQuery - defaultIndex: $defaultIndex - ) { - totalCount - edges { - node { - _id - instances - process { - args - name - } - user { - id - name - } - hosts { - name - } - } - cursor { - value - } - } - pageInfo { - activePage - fakeTotalCount - showMorePagesIndicator - } - inspect @include(if: $inspect) { - dsl - response - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx index 82f5a97e9e413..4036837024025 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx @@ -9,22 +9,26 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { PageInfoPaginated, UncommonProcessesEdges } from '../../../graphql/types'; import { inputsModel, State } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; import { generateTablePaginationOptions } from '../../../common/components/paginated_table/helpers'; import { createFilter } from '../../../common/containers/helpers'; - import { hostsModel, hostsSelectors } from '../../store'; import { - HostUncommonProcessesRequestOptions, - HostUncommonProcessesStrategyResponse, -} from '../../../../common/search_strategy/security_solution/hosts/uncommon_processes'; -import { HostsQueries } from '../../../../common/search_strategy/security_solution/hosts'; -import { DocValueFields, SortField } from '../../../../common/search_strategy'; + DocValueFields, + SortField, + PageInfoPaginated, + HostsUncommonProcessesEdges, + HostsQueries, + HostsUncommonProcessesRequestOptions, + HostsUncommonProcessesStrategyResponse, +} from '../../../../common/search_strategy'; import * as i18n from './translations'; import { ESTermQuery } from '../../../../common/typed_json'; @@ -41,13 +45,14 @@ export interface UncommonProcessesArgs { pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; totalCount: number; - uncommonProcesses: UncommonProcessesEdges[]; + uncommonProcesses: HostsUncommonProcessesEdges[]; } interface UseUncommonProcesses { docValueFields?: DocValueFields[]; filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; type: hostsModel.HostsType; @@ -57,6 +62,7 @@ export const useUncommonProcesses = ({ docValueFields, filterQuery, endDate, + indexNames, skip = false, startDate, type, @@ -65,15 +71,14 @@ export const useUncommonProcesses = ({ const { activePage, limit } = useSelector((state: State) => getUncommonProcessesSelector(state, type) ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [uncommonProcessesRequest, setUncommonProcessesRequest] = useState< - HostUncommonProcessesRequestOptions + HostsUncommonProcessesRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], factoryQueryType: HostsQueries.uncommonProcesses, filterQuery: createFilter(filterQuery), @@ -119,14 +124,14 @@ export const useUncommonProcesses = ({ ); const uncommonProcessesSearch = useCallback( - (request: HostUncommonProcessesRequestOptions) => { + (request: HostsUncommonProcessesRequestOptions) => { let didCancel = false; const asyncSearch = async () => { abortCtrl.current = new AbortController(); setLoading(true); const searchSubscription$ = data.search - .search( + .search( request, { strategy: 'securitySolutionSearchStrategy', @@ -135,7 +140,7 @@ export const useUncommonProcesses = ({ ) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setUncommonProcessesResponse((prevResponse) => ({ @@ -148,7 +153,7 @@ export const useUncommonProcesses = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -181,7 +186,7 @@ export const useUncommonProcesses = ({ setUncommonProcessesRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), @@ -197,7 +202,7 @@ export const useUncommonProcesses = ({ } return prevRequest; }); - }, [activePage, defaultIndex, docValueFields, endDate, filterQuery, limit, skip, startDate]); + }, [activePage, indexNames, docValueFields, endDate, filterQuery, limit, skip, startDate]); useEffect(() => { uncommonProcessesSearch(uncommonProcessesRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx index 11a268c7b64ad..708c8b2b40b35 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx @@ -84,6 +84,7 @@ describe('body', () => { setQuery={jest.fn()} setAbsoluteRangeDatePicker={(jest.fn() as unknown) as SetAbsoluteRangeDatePicker} hostDetailsPagePath={hostDetailsPagePath} + indexNames={[]} indexPattern={mockIndexPattern} type={type} pageFilters={mockHostDetailsPageFilters} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx index 4d4eead0e778a..284e6e27cf615 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx @@ -28,12 +28,13 @@ import { export const HostDetailsTabs = React.memo( ({ + detailName, docValueFields, - pageFilters, filterQuery, - detailName, - setAbsoluteRangeDatePicker, + indexNames, indexPattern, + pageFilters, + setAbsoluteRangeDatePicker, hostDetailsPagePath, }) => { const { from, to, isInitializing, deleteQuery, setQuery } = useGlobalTime(); @@ -73,6 +74,7 @@ export const HostDetailsTabs = React.memo( startDate: from, type, indexPattern, + indexNames, hostName: detailName, narrowDateRange, updateDateRange, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index 57e1b128ce64d..a8b46769b7363 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -9,7 +9,7 @@ import { noop } from 'lodash/fp'; import React, { useEffect, useCallback, useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { HostItem } from '../../../../common/search_strategy'; +import { HostItem, LastEventIndexKey } from '../../../../common/search_strategy'; import { SecurityPageName } from '../../../app/types'; import { UpdateDateRange } from '../../../common/components/charts/common'; import { FiltersGlobal } from '../../../common/components/filters_global'; @@ -28,8 +28,6 @@ import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; import { HostOverviewByNameQuery } from '../../containers/hosts/details'; import { useGlobalTime } from '../../../common/containers/use_global_time'; -import { useWithSource } from '../../../common/containers/source'; -import { LastEventIndexKey } from '../../../graphql/types'; import { useKibana } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; import { inputsSelectors, State } from '../../../common/store'; @@ -51,6 +49,7 @@ import { timelineSelectors } from '../../../timelines/store/timeline'; import { TimelineModel } from '../../../timelines/store/timeline/model'; import { TimelineId } from '../../../../common/types/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; const HostOverviewManage = manageQuery(HostOverview); @@ -89,7 +88,7 @@ const HostDetailsComponent = React.memo( }, [setAbsoluteRangeDatePicker] ); - const { docValueFields, indicesExist, indexPattern } = useWithSource(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); const filterQuery = convertToBuildEsQuery({ config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, @@ -111,12 +110,18 @@ const HostDetailsComponent = React.memo( + } title={detailName} /> ( > {({ isLoadingAnomaliesData, anomaliesData }) => ( ( data={hostOverview as HostItem} anomaliesData={anomaliesData} isLoadingAnomaliesData={isLoadingAnomaliesData} + indexNames={selectedPatterns} loading={loading} startDate={from} endDate={to} @@ -161,6 +168,7 @@ const HostDetailsComponent = React.memo( ( ; export type HostDetailsTabsProps = HostBodyComponentDispatchProps & HostsQueryProps & { docValueFields?: DocValueFields[]; + indexNames: string[]; pageFilters?: Filter[]; filterQuery: string; indexPattern: IIndexPattern; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx index 566f8f23efd39..b341647afdfbc 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx @@ -10,7 +10,6 @@ import { Router } from 'react-router-dom'; import { Filter } from '../../../../../../src/plugins/data/common/es_query'; import '../../common/mock/match_media'; -import { useWithSource } from '../../common/containers/source'; import { apolloClientObservable, TestProviders, @@ -25,8 +24,9 @@ import { State, createStore } from '../../common/store'; import { HostsComponentProps } from './types'; import { Hosts } from './hosts'; import { HostsTabs } from './hosts_tabs'; +import { useSourcererScope } from '../../common/containers/sourcerer'; -jest.mock('../../common/containers/source'); +jest.mock('../../common/containers/sourcerer'); // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -58,14 +58,14 @@ const mockHistory = { createHref: jest.fn(), listen: jest.fn(), }; - +const mockUseSourcererScope = useSourcererScope as jest.Mock; describe('Hosts - rendering', () => { const hostProps: HostsComponentProps = { hostsPagePath: '', }; test('it renders the Setup Instructions text when no index is available', async () => { - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ indicesExist: false, }); @@ -80,7 +80,7 @@ describe('Hosts - rendering', () => { }); test('it DOES NOT render the Setup Instructions text when an index is available', async () => { - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ indicesExist: true, indexPattern: {}, }); @@ -95,7 +95,7 @@ describe('Hosts - rendering', () => { }); test('it should render tab navigation', async () => { - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ indicesExist: true, indexPattern: {}, }); @@ -142,7 +142,7 @@ describe('Hosts - rendering', () => { }, }, ]; - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ indicesExist: true, indexPattern: { fields: [], title: 'title' }, }); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index 4b8e3cc6987ac..4835f7eff5b6f 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -22,9 +22,8 @@ import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { useFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; -import { useWithSource } from '../../common/containers/source'; import { TimelineId } from '../../../common/types/timeline'; -import { LastEventIndexKey } from '../../graphql/types'; +import { LastEventIndexKey } from '../../../common/search_strategy'; import { useKibana } from '../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../common/lib/keury'; import { inputsSelectors, State } from '../../common/store'; @@ -46,6 +45,7 @@ import { showGlobalFilters } from '../../timelines/components/timeline/helpers'; import { timelineSelectors } from '../../timelines/store/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; import { TimelineModel } from '../../timelines/store/timeline/model'; +import { useSourcererScope } from '../../common/containers/sourcerer'; export const HostsComponent = React.memo( ({ filters, graphEventId, query, setAbsoluteRangeDatePicker, hostsPagePath }) => { @@ -74,7 +74,7 @@ export const HostsComponent = React.memo( }, [setAbsoluteRangeDatePicker] ); - const { docValueFields, indicesExist, indexPattern } = useWithSource(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); const filterQuery = convertToBuildEsQuery({ config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, @@ -101,12 +101,19 @@ export const HostsComponent = React.memo( } + subtitle={ + + } title={i18n.PAGE_TITLE} /> ( to={to} filterQuery={tabsFilterQuery} isInitializing={isInitializing} + indexNames={selectedPatterns} setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker} setQuery={setQuery} from={from} type={hostsModel.HostsType.page} - indexPattern={indexPattern} hostsPagePath={hostsPagePath} /> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx index 8e2ea06fd20cb..17dd20bac2d0d 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx @@ -28,24 +28,24 @@ export const HostsTabs = memo( deleteQuery, docValueFields, filterQuery, - setAbsoluteRangeDatePicker, - to, from, - setQuery, + indexNames, isInitializing, - type, - indexPattern, hostsPagePath, + setAbsoluteRangeDatePicker, + setQuery, + to, + type, }) => { const tabProps = { deleteQuery, endDate: to, filterQuery, + indexNames, skip: isInitializing, setQuery, startDate: from, type, - indexPattern, narrowDateRange: useCallback( (score: Anomaly, interval: string) => { const fromTo = scoreIntervalToDateTime(score, interval); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx index d3fc68874ce91..efce312fd85f2 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx @@ -66,6 +66,7 @@ const AuthenticationsQueryTabBodyComponent: React.FC docValueFields, endDate, filterQuery, + indexNames, skip, setQuery, startDate, @@ -74,7 +75,15 @@ const AuthenticationsQueryTabBodyComponent: React.FC const [ loading, { authentications, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, - ] = useAuthentications({ docValueFields, endDate, filterQuery, skip, startDate, type }); + ] = useAuthentications({ + docValueFields, + endDate, + filterQuery, + indexNames, + skip, + startDate, + type, + }); useEffect(() => { return () => { @@ -90,6 +99,7 @@ const AuthenticationsQueryTabBodyComponent: React.FC endDate={endDate} filterQuery={filterQuery} id={ID} + indexNames={indexNames} setQuery={setQuery} startDate={startDate} {...histogramConfigs} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx index be8412caf7732..e30071ec04f0c 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx @@ -20,6 +20,7 @@ import { useFullScreen } from '../../../common/containers/use_full_screen'; import * as i18n from '../translations'; import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; const EVENTS_HISTOGRAM_ID = 'eventsHistogramQuery'; @@ -54,6 +55,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ deleteQuery, endDate, filterQuery, + indexNames, pageFilters, setQuery, startDate, @@ -85,6 +87,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ setQuery={setQuery} startDate={startDate} id={EVENTS_HISTOGRAM_ID} + indexNames={indexNames} {...histogramConfigs} /> )} @@ -92,6 +95,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ defaultModel={eventsDefaultModel} end={endDate} id={TimelineId.hostsPageEvents} + scopeId={SourcererScopeName.default} start={startDate} pageFilters={pageFilters} /> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx index f8dcf9635c053..deda4b618fa64 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx @@ -18,7 +18,7 @@ export const HostsQueryTabBody = ({ docValueFields, endDate, filterQuery, - indexPattern, + indexNames, skip, setQuery, startDate, @@ -27,7 +27,7 @@ export const HostsQueryTabBody = ({ const [ loading, { hosts, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, - ] = useAllHost({ docValueFields, endDate, filterQuery, skip, startDate, type }); + ] = useAllHost({ docValueFields, endDate, filterQuery, indexNames, skip, startDate, type }); return ( { })}${appendSearch(search)}`; }; -const isDefaultOrMissing = ( - value: number | string | undefined, - defaultValue: number | undefined -) => { +const isDefaultOrMissing = (value: T | undefined, defaultValue: T) => { return value === undefined || value === defaultValue; }; diff --git a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx index 372916581b35d..1d27c75de009d 100644 --- a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx @@ -35,7 +35,12 @@ export const AdministrationListPage: FC - + {actions} diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index 235f150637116..40c982cfc071b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -203,6 +203,7 @@ export const PolicyDetails = React.memo(() => { )} ; + deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise; createTrustedApp(request: PostTrustedAppCreateRequest): Promise; } @@ -30,6 +37,10 @@ export class TrustedAppsHttpService implements TrustedAppsService { }); } + async deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise { + return this.http.delete(resolvePathVariables(TRUSTED_APPS_DELETE_API, request)); + } + async createTrustedApp(request: PostTrustedAppCreateRequest) { return this.http.post(TRUSTED_APPS_CREATE_API, { body: JSON.stringify(request), diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.test.ts new file mode 100644 index 0000000000000..c937b318e8961 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resolvePathVariables } from './utils'; + +describe('utils', () => { + describe('resolvePathVariables', () => { + it('should resolve defined variables', () => { + expect(resolvePathVariables('/segment1/{var1}/segment2', { var1: 'value1' })).toBe( + '/segment1/value1/segment2' + ); + }); + + it('should not resolve undefined variables', () => { + expect(resolvePathVariables('/segment1/{var1}/segment2', {})).toBe( + '/segment1/{var1}/segment2' + ); + }); + + it('should ignore unused variables', () => { + expect(resolvePathVariables('/segment1/{var1}/segment2', { var2: 'value2' })).toBe( + '/segment1/{var1}/segment2' + ); + }); + + it('should replace multiple variable occurences', () => { + expect(resolvePathVariables('/{var1}/segment1/{var1}', { var1: 'value1' })).toBe( + '/value1/segment1/value1' + ); + }); + + it('should replace multiple variables', () => { + const path = resolvePathVariables('/{var1}/segment1/{var2}', { + var1: 'value1', + var2: 'value2', + }); + + expect(path).toBe('/value1/segment1/value2'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.ts new file mode 100644 index 0000000000000..075d74da018b4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.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; + * you may not use this file except in compliance with the Elastic License. + */ + +export const resolvePathVariables = (path: string, variables: { [K: string]: string | number }) => + Object.keys(variables).reduce((acc, paramName) => { + return acc.replace(new RegExp(`\{${paramName}\}`, 'g'), String(variables[paramName])); + }, path); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts index 5e00d833981ed..534a4ec14861b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts @@ -13,6 +13,7 @@ import { isLoadingResourceState, isLoadedResourceState, isFailedResourceState, + isStaleResourceState, getLastLoadedResourceState, getCurrentResourceError, isOutdatedResourceState, @@ -137,6 +138,24 @@ describe('AsyncResourceState', () => { expect(isFailedResourceState(failedResourceStateInitially)).toBe(true); }); }); + + describe('isStaleResourceState()', () => { + it('returns true for UninitialisedResourceState', () => { + expect(isStaleResourceState(uninitialisedResourceState)).toBe(true); + }); + + it('returns false for LoadingResourceState', () => { + expect(isStaleResourceState(loadingResourceStateInitially)).toBe(false); + }); + + it('returns true for LoadedResourceState', () => { + expect(isStaleResourceState(loadedResourceState)).toBe(true); + }); + + it('returns true for FailedResourceState', () => { + expect(isStaleResourceState(failedResourceStateInitially)).toBe(true); + }); + }); }); describe('functions', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts index 4639a50a61865..bb868418e7f0d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts @@ -35,7 +35,7 @@ export interface UninitialisedResourceState { * @param Data - type of the data that is referenced by resource state * @param Error - type of the error that can happen during attempt to update data */ -export interface LoadingResourceState { +export interface LoadingResourceState { type: 'LoadingResourceState'; previousState: StaleResourceState; } @@ -46,7 +46,7 @@ export interface LoadingResourceState { * * @param Data - type of the data that is referenced by resource state */ -export interface LoadedResourceState { +export interface LoadedResourceState { type: 'LoadedResourceState'; data: Data; } @@ -59,7 +59,7 @@ export interface LoadedResourceState { * @param Data - type of the data that is referenced by resource state * @param Error - type of the error that can happen during attempt to update data */ -export interface FailedResourceState { +export interface FailedResourceState { type: 'FailedResourceState'; error: Error; lastLoadedState?: LoadedResourceState; @@ -71,7 +71,7 @@ export interface FailedResourceState { * @param Data - type of the data that is referenced by resource state * @param Error - type of the error that can happen during attempt to update data */ -export type StaleResourceState = +export type StaleResourceState = | UninitialisedResourceState | LoadedResourceState | FailedResourceState; @@ -82,7 +82,7 @@ export type StaleResourceState = * @param Data - type of the data that is referenced by resource state * @param Error - type of the error that can happen during attempt to update data */ -export type AsyncResourceState = +export type AsyncResourceState = | UninitialisedResourceState | LoadingResourceState | LoadedResourceState @@ -106,6 +106,13 @@ export const isFailedResourceState = ( state: Immutable> ): state is Immutable> => state.type === 'FailedResourceState'; +export const isStaleResourceState = ( + state: Immutable> +): state is Immutable> => + isUninitialisedResourceState(state) || + isLoadedResourceState(state) || + isFailedResourceState(state); + // Set of functions to work with AsyncResourceState export const getLastLoadedResourceState = ( diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts index 071557ec1a815..4c38ac0c4239a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ServerApiError } from '../../../../common/types'; import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps'; import { AsyncResourceState } from '.'; import { TrustedAppsUrlParams } from '../types'; -import { ServerApiError } from '../../../../common/types'; export interface PaginationInfo { index: number; @@ -18,6 +18,7 @@ export interface TrustedAppsListData { items: TrustedApp[]; totalItemsCount: number; paginationInfo: PaginationInfo; + timestamp: number; } /** Store State when an API request has been sent to create a new trusted app entry */ @@ -42,8 +43,14 @@ export interface TrustedAppsListPageState { listView: { currentListResourceState: AsyncResourceState; currentPaginationInfo: PaginationInfo; + freshDataTimestamp: number; show: TrustedAppsUrlParams['show'] | undefined; }; + deletionDialog: { + entry?: TrustedApp; + confirmed: boolean; + submissionResourceState: AsyncResourceState; + }; createView: | undefined | TrustedAppCreatePending diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts index 3a43ffe58262c..5315087c09655 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Action } from 'redux'; + +import { TrustedApp } from '../../../../../common/endpoint/types'; import { AsyncResourceState, TrustedAppCreateFailure, @@ -12,12 +15,30 @@ import { TrustedAppsListData, } from '../state'; -export interface TrustedAppsListResourceStateChanged { - type: 'trustedAppsListResourceStateChanged'; +export type TrustedAppsListDataOutdated = Action<'trustedAppsListDataOutdated'>; + +interface ResourceStateChanged extends Action { + payload: { newState: AsyncResourceState }; +} + +export type TrustedAppsListResourceStateChanged = ResourceStateChanged< + 'trustedAppsListResourceStateChanged', + TrustedAppsListData +>; + +export type TrustedAppDeletionSubmissionResourceStateChanged = ResourceStateChanged< + 'trustedAppDeletionSubmissionResourceStateChanged' +>; + +export type TrustedAppDeletionDialogStarted = Action<'trustedAppDeletionDialogStarted'> & { payload: { - newState: AsyncResourceState; + entry: TrustedApp; }; -} +}; + +export type TrustedAppDeletionDialogConfirmed = Action<'trustedAppDeletionDialogConfirmed'>; + +export type TrustedAppDeletionDialogClosed = Action<'trustedAppDeletionDialogClosed'>; export interface UserClickedSaveNewTrustedAppButton { type: 'userClickedSaveNewTrustedAppButton'; @@ -35,7 +56,12 @@ export interface ServerReturnedCreateTrustedAppFailure { } export type TrustedAppsPageAction = + | TrustedAppsListDataOutdated | TrustedAppsListResourceStateChanged + | TrustedAppDeletionSubmissionResourceStateChanged + | TrustedAppDeletionDialogStarted + | TrustedAppDeletionDialogConfirmed + | TrustedAppDeletionDialogClosed | UserClickedSaveNewTrustedAppButton | ServerReturnedCreateTrustedAppSuccess | ServerReturnedCreateTrustedAppFailure; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts index e5f00ee0ccf81..19c2d3a62781f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts @@ -10,8 +10,10 @@ import { createSpyMiddleware } from '../../../../common/store/test_utils'; import { createFailedListViewWithPagination, + createListLoadedResourceState, createLoadedListViewWithPagination, createLoadingListViewWithPagination, + createSampleTrustedApp, createSampleTrustedApps, createServerApiError, createUserChangedUrlAction, @@ -22,6 +24,14 @@ import { PaginationInfo, TrustedAppsListPageState } from '../state'; import { initialTrustedAppsPageState, trustedAppsPageReducer } from './reducer'; import { createTrustedAppsPageMiddleware } from './middleware'; +const initialNow = 111111; +const dateNowMock = jest.fn(); +dateNowMock.mockReturnValue(initialNow); + +Date.now = dateNowMock; + +const initialState = initialTrustedAppsPageState(); + const createGetTrustedListAppsResponse = (pagination: PaginationInfo, totalItemsCount: number) => ({ data: createSampleTrustedApps(pagination), page: pagination.index, @@ -31,6 +41,7 @@ const createGetTrustedListAppsResponse = (pagination: PaginationInfo, totalItems const createTrustedAppsServiceMock = (): jest.Mocked => ({ getTrustedAppsList: jest.fn(), + deleteTrustedApp: jest.fn(), createTrustedApp: jest.fn(), }); @@ -50,13 +61,19 @@ const createStoreSetup = (trustedAppsService: TrustedAppsService) => { }; describe('middleware', () => { - describe('refreshing list resource state', () => { + beforeEach(() => { + dateNowMock.mockReturnValue(initialNow); + }); + + describe('initial state', () => { it('sets initial state properly', async () => { expect(createStoreSetup(createTrustedAppsServiceMock()).store.getState()).toStrictEqual( - initialTrustedAppsPageState + initialState ); }); + }); + describe('refreshing list resource state', () => { it('refreshes the list when location changes and data gets outdated', async () => { const pagination = { index: 2, size: 50 }; const service = createTrustedAppsServiceMock(); @@ -69,17 +86,17 @@ describe('middleware', () => { store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50')); expect(store.getState()).toStrictEqual({ - listView: createLoadingListViewWithPagination(pagination), + ...initialState, + listView: createLoadingListViewWithPagination(initialNow, pagination), active: true, - createView: undefined, }); await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); expect(store.getState()).toStrictEqual({ - listView: createLoadedListViewWithPagination(pagination, pagination, 500), + ...initialState, + listView: createLoadedListViewWithPagination(initialNow, pagination, pagination, 500), active: true, - createView: undefined, }); }); @@ -100,13 +117,50 @@ describe('middleware', () => { expect(service.getTrustedAppsList).toBeCalledTimes(1); expect(store.getState()).toStrictEqual({ - listView: createLoadedListViewWithPagination(pagination, pagination, 500), + ...initialState, + listView: createLoadedListViewWithPagination(initialNow, pagination, pagination, 500), active: true, - createView: undefined, }); }); - it('set list resource state to faile when failing to load data', async () => { + it('refreshes the list when data gets outdated with and outdate action', async () => { + const newNow = 222222; + const pagination = { index: 0, size: 10 }; + const service = createTrustedAppsServiceMock(); + const { store, spyMiddleware } = createStoreSetup(service); + + service.getTrustedAppsList.mockResolvedValue( + createGetTrustedListAppsResponse(pagination, 500) + ); + + store.dispatch(createUserChangedUrlAction('/trusted_apps')); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + dateNowMock.mockReturnValue(newNow); + + store.dispatch({ type: 'trustedAppsListDataOutdated' }); + + expect(store.getState()).toStrictEqual({ + ...initialState, + listView: createLoadingListViewWithPagination( + newNow, + pagination, + createListLoadedResourceState(pagination, 500, initialNow) + ), + active: true, + }); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + expect(store.getState()).toStrictEqual({ + ...initialState, + listView: createLoadedListViewWithPagination(newNow, pagination, pagination, 500), + active: true, + }); + }); + + it('set list resource state to failed when failing to load data', async () => { const service = createTrustedAppsServiceMock(); const { store, spyMiddleware } = createStoreSetup(service); @@ -117,12 +171,13 @@ describe('middleware', () => { await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); expect(store.getState()).toStrictEqual({ + ...initialState, listView: createFailedListViewWithPagination( + initialNow, { index: 2, size: 50 }, createServerApiError('Internal Server Error') ), active: true, - createView: undefined, }); const infiniteLoopTest = async () => { @@ -132,4 +187,151 @@ describe('middleware', () => { await expect(infiniteLoopTest).rejects.not.toBeNull(); }); }); + + describe('submitting deletion dialog', () => { + const newNow = 222222; + const entry = createSampleTrustedApp(3); + const notFoundError = createServerApiError('Not Found'); + const pagination = { index: 0, size: 10 }; + const getTrustedAppsListResponse = createGetTrustedListAppsResponse(pagination, 500); + const listView = createLoadedListViewWithPagination(initialNow, pagination, pagination, 500); + const listViewNew = createLoadedListViewWithPagination(newNow, pagination, pagination, 500); + const testStartState = { ...initialState, listView, active: true }; + + it('does not submit when entry is undefined', async () => { + const service = createTrustedAppsServiceMock(); + const { store, spyMiddleware } = createStoreSetup(service); + + service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); + service.deleteTrustedApp.mockResolvedValue(); + + store.dispatch(createUserChangedUrlAction('/trusted_apps')); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + + expect(store.getState()).toStrictEqual({ + ...testStartState, + deletionDialog: { ...testStartState.deletionDialog, confirmed: true }, + }); + }); + + it('submits successfully when entry is defined', async () => { + const service = createTrustedAppsServiceMock(); + const { store, spyMiddleware } = createStoreSetup(service); + + service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); + service.deleteTrustedApp.mockResolvedValue(); + + store.dispatch(createUserChangedUrlAction('/trusted_apps')); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + dateNowMock.mockReturnValue(newNow); + + store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } }); + store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + + expect(store.getState()).toStrictEqual({ + ...testStartState, + deletionDialog: { + entry, + confirmed: true, + submissionResourceState: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }, + }); + + await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged'); + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + expect(store.getState()).toStrictEqual({ ...testStartState, listView: listViewNew }); + expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' }); + expect(service.deleteTrustedApp).toBeCalledTimes(1); + }); + + it('does not submit twice', async () => { + const service = createTrustedAppsServiceMock(); + const { store, spyMiddleware } = createStoreSetup(service); + + service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); + service.deleteTrustedApp.mockResolvedValue(); + + store.dispatch(createUserChangedUrlAction('/trusted_apps')); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + dateNowMock.mockReturnValue(newNow); + + store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } }); + store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + + expect(store.getState()).toStrictEqual({ + ...testStartState, + deletionDialog: { + entry, + confirmed: true, + submissionResourceState: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }, + }); + + await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged'); + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + expect(store.getState()).toStrictEqual({ ...testStartState, listView: listViewNew }); + expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' }); + expect(service.deleteTrustedApp).toBeCalledTimes(1); + }); + + it('does not submit when server response with failure', async () => { + const service = createTrustedAppsServiceMock(); + const { store, spyMiddleware } = createStoreSetup(service); + + service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); + service.deleteTrustedApp.mockRejectedValue(notFoundError); + + store.dispatch(createUserChangedUrlAction('/trusted_apps')); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } }); + store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + + expect(store.getState()).toStrictEqual({ + ...testStartState, + deletionDialog: { + entry, + confirmed: true, + submissionResourceState: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }, + }); + + await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged'); + + expect(store.getState()).toStrictEqual({ + ...testStartState, + deletionDialog: { + entry, + confirmed: true, + submissionResourceState: { + type: 'FailedResourceState', + error: notFoundError, + lastLoadedState: undefined, + }, + }, + }); + expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' }); + expect(service.deleteTrustedApp).toBeCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts index bf9cacff5caf0..dd96c8d807048 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts @@ -16,15 +16,22 @@ import { TrustedAppsHttpService, TrustedAppsService } from '../service'; import { AsyncResourceState, + getLastLoadedResourceState, + isStaleResourceState, StaleResourceState, TrustedAppsListData, TrustedAppsListPageState, } from '../state'; -import { TrustedAppsListResourceStateChanged } from './action'; +import { + TrustedAppDeletionSubmissionResourceStateChanged, + TrustedAppsListResourceStateChanged, +} from './action'; import { getCurrentListResourceState, + getDeletionDialogEntry, + getDeletionSubmissionResourceState, getLastLoadedListResourceState, getListCurrentPageIndex, getListCurrentPageSize, @@ -40,46 +47,98 @@ const createTrustedAppsListResourceStateChangedAction = ( payload: { newState }, }); -const refreshList = async ( +const refreshListIfNeeded = async ( store: ImmutableMiddlewareAPI, trustedAppsService: TrustedAppsService ) => { - store.dispatch( - createTrustedAppsListResourceStateChangedAction({ - type: 'LoadingResourceState', - // need to think on how to avoid the casting - previousState: getCurrentListResourceState(store.getState()) as Immutable< - StaleResourceState - >, - }) - ); - - try { - const pageIndex = getListCurrentPageIndex(store.getState()); - const pageSize = getListCurrentPageSize(store.getState()); - const response = await trustedAppsService.getTrustedAppsList({ - page: pageIndex + 1, - per_page: pageSize, - }); - + if (needsRefreshOfListData(store.getState())) { store.dispatch( createTrustedAppsListResourceStateChangedAction({ - type: 'LoadedResourceState', - data: { - items: response.data, - totalItemsCount: response.total, - paginationInfo: { index: pageIndex, size: pageSize }, - }, + type: 'LoadingResourceState', + // need to think on how to avoid the casting + previousState: getCurrentListResourceState(store.getState()) as Immutable< + StaleResourceState + >, }) ); - } catch (error) { + + try { + const pageIndex = getListCurrentPageIndex(store.getState()); + const pageSize = getListCurrentPageSize(store.getState()); + const response = await trustedAppsService.getTrustedAppsList({ + page: pageIndex + 1, + per_page: pageSize, + }); + + store.dispatch( + createTrustedAppsListResourceStateChangedAction({ + type: 'LoadedResourceState', + data: { + items: response.data, + totalItemsCount: response.total, + paginationInfo: { index: pageIndex, size: pageSize }, + timestamp: Date.now(), + }, + }) + ); + } catch (error) { + store.dispatch( + createTrustedAppsListResourceStateChangedAction({ + type: 'FailedResourceState', + error, + lastLoadedState: getLastLoadedListResourceState(store.getState()), + }) + ); + } + } +}; + +const createTrustedAppDeletionSubmissionResourceStateChanged = ( + newState: Immutable +): Immutable => ({ + type: 'trustedAppDeletionSubmissionResourceStateChanged', + payload: { newState }, +}); + +const submitDeletionIfNeeded = async ( + store: ImmutableMiddlewareAPI, + trustedAppsService: TrustedAppsService +) => { + const submissionResourceState = getDeletionSubmissionResourceState(store.getState()); + const entry = getDeletionDialogEntry(store.getState()); + + if (isStaleResourceState(submissionResourceState) && entry !== undefined) { store.dispatch( - createTrustedAppsListResourceStateChangedAction({ - type: 'FailedResourceState', - error, - lastLoadedState: getLastLoadedListResourceState(store.getState()), + createTrustedAppDeletionSubmissionResourceStateChanged({ + type: 'LoadingResourceState', + previousState: submissionResourceState, }) ); + + try { + await trustedAppsService.deleteTrustedApp({ id: entry.id }); + + store.dispatch( + createTrustedAppDeletionSubmissionResourceStateChanged({ + type: 'LoadedResourceState', + data: null, + }) + ); + store.dispatch({ + type: 'trustedAppDeletionDialogClosed', + }); + store.dispatch({ + type: 'trustedAppsListDataOutdated', + }); + } catch (error) { + store.dispatch( + createTrustedAppDeletionSubmissionResourceStateChanged({ + type: 'FailedResourceState', + error, + lastLoadedState: getLastLoadedResourceState(submissionResourceState), + }) + ); + } } }; @@ -102,7 +161,9 @@ const createTrustedApp = async ( data: createdTrustedApp, }, }); - refreshList(store, trustedAppsService); + store.dispatch({ + type: 'trustedAppsListDataOutdated', + }); } catch (error) { dispatch({ type: 'serverReturnedCreateTrustedAppFailure', @@ -122,8 +183,12 @@ export const createTrustedAppsPageMiddleware = ( next(action); // TODO: need to think if failed state is a good condition to consider need for refresh - if (action.type === 'userChangedUrl' && needsRefreshOfListData(store.getState())) { - await refreshList(store, trustedAppsService); + if (action.type === 'userChangedUrl' || action.type === 'trustedAppsListDataOutdated') { + await refreshListIfNeeded(store, trustedAppsService); + } + + if (action.type === 'trustedAppDeletionDialogConfirmed') { + await submitDeletionIfNeeded(store, trustedAppsService); } if (action.type === 'userClickedSaveNewTrustedAppButton') { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts index 76dd4b48e63d2..228f0932edd28 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts @@ -4,93 +4,180 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AsyncResourceState } from '../state'; import { initialTrustedAppsPageState, trustedAppsPageReducer } from './reducer'; import { + createSampleTrustedApp, createListLoadedResourceState, createLoadedListViewWithPagination, - createTrustedAppsListResourceStateChangedAction, createUserChangedUrlAction, + createTrustedAppsListResourceStateChangedAction, } from '../test_utils'; +const initialNow = 111111; +const dateNowMock = jest.fn(); +dateNowMock.mockReturnValue(initialNow); + +Date.now = dateNowMock; + +const initialState = initialTrustedAppsPageState(); + describe('reducer', () => { describe('UserChangedUrl', () => { it('makes page state active and extracts pagination parameters', () => { const result = trustedAppsPageReducer( - initialTrustedAppsPageState, + initialState, createUserChangedUrlAction('/trusted_apps', '?page_index=5&page_size=50') ); expect(result).toStrictEqual({ - listView: { - ...initialTrustedAppsPageState.listView, - currentPaginationInfo: { index: 5, size: 50 }, - }, + ...initialState, + listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } }, active: true, - createView: undefined, }); }); it('extracts default pagination parameters when none provided', () => { const result = trustedAppsPageReducer( { - ...initialTrustedAppsPageState, - listView: { - ...initialTrustedAppsPageState.listView, - currentPaginationInfo: { index: 5, size: 50 }, - }, + ...initialState, + listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } }, }, createUserChangedUrlAction('/trusted_apps', '?page_index=b&page_size=60') ); - expect(result).toStrictEqual({ - ...initialTrustedAppsPageState, - active: true, - }); + expect(result).toStrictEqual({ ...initialState, active: true }); }); it('extracts default pagination parameters when invalid provided', () => { const result = trustedAppsPageReducer( { - ...initialTrustedAppsPageState, - listView: { - ...initialTrustedAppsPageState.listView, - currentPaginationInfo: { index: 5, size: 50 }, - }, + ...initialState, + listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } }, }, createUserChangedUrlAction('/trusted_apps') ); - expect(result).toStrictEqual({ - ...initialTrustedAppsPageState, - active: true, - }); + expect(result).toStrictEqual({ ...initialState, active: true }); }); it('makes page state inactive and resets list to uninitialised state when navigating away', () => { const result = trustedAppsPageReducer( - { listView: createLoadedListViewWithPagination(), active: true, createView: undefined }, + { ...initialState, listView: createLoadedListViewWithPagination(initialNow), active: true }, createUserChangedUrlAction('/endpoints') ); - expect(result).toStrictEqual(initialTrustedAppsPageState); + expect(result).toStrictEqual(initialState); }); }); describe('TrustedAppsListResourceStateChanged', () => { it('sets the current list resource state', () => { - const listResourceState = createListLoadedResourceState({ index: 3, size: 50 }, 200); + const listResourceState = createListLoadedResourceState( + { index: 3, size: 50 }, + 200, + initialNow + ); const result = trustedAppsPageReducer( - initialTrustedAppsPageState, + initialState, createTrustedAppsListResourceStateChangedAction(listResourceState) ); expect(result).toStrictEqual({ - ...initialTrustedAppsPageState, - listView: { - ...initialTrustedAppsPageState.listView, - currentListResourceState: listResourceState, + ...initialState, + listView: { ...initialState.listView, currentListResourceState: listResourceState }, + }); + }); + }); + + describe('TrustedAppsListDataOutdated', () => { + it('sets the list view freshness timestamp', () => { + const newNow = 222222; + dateNowMock.mockReturnValue(newNow); + + const result = trustedAppsPageReducer(initialState, { type: 'trustedAppsListDataOutdated' }); + + expect(result).toStrictEqual({ + ...initialState, + listView: { ...initialState.listView, freshDataTimestamp: newNow }, + }); + }); + }); + + describe('TrustedAppDeletionSubmissionResourceStateChanged', () => { + it('sets the deletion dialog submission resource state', () => { + const submissionResourceState: AsyncResourceState = { + type: 'LoadedResourceState', + data: null, + }; + const result = trustedAppsPageReducer(initialState, { + type: 'trustedAppDeletionSubmissionResourceStateChanged', + payload: { newState: submissionResourceState }, + }); + + expect(result).toStrictEqual({ + ...initialState, + deletionDialog: { ...initialState.deletionDialog, submissionResourceState }, + }); + }); + }); + + describe('TrustedAppDeletionDialogStarted', () => { + it('sets the deletion dialog state to started', () => { + const entry = createSampleTrustedApp(3); + const result = trustedAppsPageReducer(initialState, { + type: 'trustedAppDeletionDialogStarted', + payload: { entry }, + }); + + expect(result).toStrictEqual({ + ...initialState, + deletionDialog: { ...initialState.deletionDialog, entry }, + }); + }); + }); + + describe('TrustedAppDeletionDialogConfirmed', () => { + it('sets the deletion dialog state to confirmed', () => { + const entry = createSampleTrustedApp(3); + const result = trustedAppsPageReducer( + { + ...initialState, + deletionDialog: { + entry, + confirmed: false, + submissionResourceState: { type: 'UninitialisedResourceState' }, + }, + }, + { type: 'trustedAppDeletionDialogConfirmed' } + ); + + expect(result).toStrictEqual({ + ...initialState, + deletionDialog: { + entry, + confirmed: true, + submissionResourceState: { type: 'UninitialisedResourceState' }, }, }); }); }); + + describe('TrustedAppDeletionDialogClosed', () => { + it('sets the deletion dialog state to confirmed', () => { + const result = trustedAppsPageReducer( + { + ...initialState, + deletionDialog: { + entry: createSampleTrustedApp(3), + confirmed: true, + submissionResourceState: { type: 'UninitialisedResourceState' }, + }, + }, + { type: 'trustedAppDeletionDialogClosed' } + ); + + expect(result).toStrictEqual(initialState); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts index d824a6e95c8d5..ec210254bf76f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts @@ -19,9 +19,14 @@ import { } from '../../../common/constants'; import { + TrustedAppDeletionDialogClosed, + TrustedAppDeletionDialogConfirmed, + TrustedAppDeletionDialogStarted, + TrustedAppDeletionSubmissionResourceStateChanged, + TrustedAppsListDataOutdated, + TrustedAppsListResourceStateChanged, ServerReturnedCreateTrustedAppFailure, ServerReturnedCreateTrustedAppSuccess, - TrustedAppsListResourceStateChanged, UserClickedSaveNewTrustedAppButton, } from './action'; import { TrustedAppsListPageState } from '../state'; @@ -41,6 +46,16 @@ const isTrustedAppsPageLocation = (location: Immutable) => { ); }; +const trustedAppsListDataOutdated: CaseReducer = (state, action) => { + return { + ...state, + listView: { + ...state.listView, + freshDataTimestamp: Date.now(), + }, + }; +}; + const trustedAppsListResourceStateChanged: CaseReducer = ( state, action @@ -54,6 +69,44 @@ const trustedAppsListResourceStateChanged: CaseReducer = ( + state, + action +) => { + return { + ...state, + deletionDialog: { ...state.deletionDialog, submissionResourceState: action.payload.newState }, + }; +}; + +const trustedAppDeletionDialogStarted: CaseReducer = ( + state, + action +) => { + return { + ...state, + deletionDialog: { + entry: action.payload.entry, + confirmed: false, + submissionResourceState: { type: 'UninitialisedResourceState' }, + }, + }; +}; + +const trustedAppDeletionDialogConfirmed: CaseReducer = ( + state, + action +) => { + return { ...state, deletionDialog: { ...state.deletionDialog, confirmed: true } }; +}; + +const trustedAppDeletionDialogClosed: CaseReducer = ( + state, + action +) => { + return { ...state, deletionDialog: initialDeletionDialogState() }; +}; + const userChangedUrl: CaseReducer = (state, action) => { if (isTrustedAppsPageLocation(action.payload)) { const parsedUrlsParams = parse(action.payload.search.slice(1)); @@ -75,7 +128,7 @@ const userChangedUrl: CaseReducer = (state, action) => { active: true, }; } else { - return initialTrustedAppsPageState; + return initialTrustedAppsPageState(); } }; @@ -90,27 +143,49 @@ const trustedAppsCreateResourceChanged: CaseReducer< }; }; -export const initialTrustedAppsPageState: TrustedAppsListPageState = { +const initialDeletionDialogState = (): TrustedAppsListPageState['deletionDialog'] => ({ + confirmed: false, + submissionResourceState: { type: 'UninitialisedResourceState' }, +}); + +export const initialTrustedAppsPageState = (): TrustedAppsListPageState => ({ listView: { currentListResourceState: { type: 'UninitialisedResourceState' }, currentPaginationInfo: { index: MANAGEMENT_DEFAULT_PAGE, size: MANAGEMENT_DEFAULT_PAGE_SIZE, }, + freshDataTimestamp: Date.now(), show: undefined, }, + deletionDialog: initialDeletionDialogState(), createView: undefined, active: false, -}; +}); export const trustedAppsPageReducer: StateReducer = ( - state = initialTrustedAppsPageState, + state = initialTrustedAppsPageState(), action ) => { switch (action.type) { + case 'trustedAppsListDataOutdated': + return trustedAppsListDataOutdated(state, action); + case 'trustedAppsListResourceStateChanged': return trustedAppsListResourceStateChanged(state, action); + case 'trustedAppDeletionSubmissionResourceStateChanged': + return trustedAppDeletionSubmissionResourceStateChanged(state, action); + + case 'trustedAppDeletionDialogStarted': + return trustedAppDeletionDialogStarted(state, action); + + case 'trustedAppDeletionDialogConfirmed': + return trustedAppDeletionDialogConfirmed(state, action); + + case 'trustedAppDeletionDialogClosed': + return trustedAppDeletionDialogClosed(state, action); + case 'userChangedUrl': return userChangedUrl(state, action); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts index 453afa1befa6b..0be4d0b05acc4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AsyncResourceState, TrustedAppsListPageState } from '../state'; +import { initialTrustedAppsPageState } from './reducer'; import { getCurrentListResourceState, getLastLoadedListResourceState, @@ -14,6 +16,12 @@ import { getListTotalItemsCount, isListLoading, needsRefreshOfListData, + isDeletionDialogOpen, + isDeletionInProgress, + isDeletionSuccessful, + getDeletionError, + getDeletionDialogEntry, + getDeletionSubmissionResourceState, } from './selectors'; import { @@ -23,96 +31,118 @@ import { createListFailedResourceState, createListLoadedResourceState, createLoadedListViewWithPagination, + createSampleTrustedApp, createSampleTrustedApps, + createServerApiError, createUninitialisedResourceState, } from '../test_utils'; +const initialNow = 111111; +const dateNowMock = jest.fn(); +dateNowMock.mockReturnValue(initialNow); + +Date.now = dateNowMock; + +const initialState = initialTrustedAppsPageState(); + +const createStateWithDeletionSubmissionResourceState = ( + submissionResourceState: AsyncResourceState +): TrustedAppsListPageState => ({ + ...initialState, + deletionDialog: { ...initialState.deletionDialog, submissionResourceState }, +}); + describe('selectors', () => { describe('needsRefreshOfListData()', () => { it('returns false for outdated resource state and inactive state', () => { - expect( - needsRefreshOfListData({ - listView: createDefaultListView(), - active: false, - createView: undefined, - }) - ).toBe(false); + expect(needsRefreshOfListData(initialState)).toBe(false); }); it('returns true for outdated resource state and active state', () => { - expect( - needsRefreshOfListData({ - listView: createDefaultListView(), - active: true, - createView: undefined, - }) - ).toBe(true); + expect(needsRefreshOfListData({ ...initialState, active: true })).toBe(true); }); it('returns true when current loaded page index is outdated', () => { - const listView = createLoadedListViewWithPagination({ index: 1, size: 20 }); + const listView = createLoadedListViewWithPagination(initialNow, { index: 1, size: 20 }); - expect(needsRefreshOfListData({ listView, active: true, createView: undefined })).toBe(true); + expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true); }); it('returns true when current loaded page size is outdated', () => { - const listView = createLoadedListViewWithPagination({ index: 0, size: 50 }); + const listView = createLoadedListViewWithPagination(initialNow, { index: 0, size: 50 }); + + expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true); + }); + + it('returns true when current loaded data timestamp is outdated', () => { + const listView = { + ...createLoadedListViewWithPagination(111111), + freshDataTimestamp: 222222, + }; - expect(needsRefreshOfListData({ listView, active: true, createView: undefined })).toBe(true); + expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true); }); it('returns false when current loaded data is up to date', () => { - const listView = createLoadedListViewWithPagination(); + const listView = createLoadedListViewWithPagination(initialNow); - expect(needsRefreshOfListData({ listView, active: true, createView: undefined })).toBe(false); + expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(false); }); }); describe('getCurrentListResourceState()', () => { it('returns current list resource state', () => { - const listView = createDefaultListView(); + const state = { ...initialState, listView: createDefaultListView(initialNow) }; - expect( - getCurrentListResourceState({ listView, active: false, createView: undefined }) - ).toStrictEqual(createUninitialisedResourceState()); + expect(getCurrentListResourceState(state)).toStrictEqual(createUninitialisedResourceState()); }); }); describe('getLastLoadedListResourceState()', () => { it('returns last loaded list resource state', () => { - const listView = { - currentListResourceState: createListComplexLoadingResourceState( - createDefaultPaginationInfo(), - 200 - ), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListComplexLoadingResourceState( + createDefaultPaginationInfo(), + 200, + initialNow + ), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect( - getLastLoadedListResourceState({ listView, active: false, createView: undefined }) - ).toStrictEqual(createListLoadedResourceState(createDefaultPaginationInfo(), 200)); + expect(getLastLoadedListResourceState(state)).toStrictEqual( + createListLoadedResourceState(createDefaultPaginationInfo(), 200, initialNow) + ); }); }); describe('getListItems()', () => { it('returns empty list when no valid data loaded', () => { - expect( - getListItems({ listView: createDefaultListView(), active: false, createView: undefined }) - ).toStrictEqual([]); + const state = { ...initialState, listView: createDefaultListView(initialNow) }; + + expect(getListItems(state)).toStrictEqual([]); }); it('returns last loaded list items', () => { - const listView = { - currentListResourceState: createListComplexLoadingResourceState( - createDefaultPaginationInfo(), - 200 - ), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListComplexLoadingResourceState( + createDefaultPaginationInfo(), + 200, + initialNow + ), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect(getListItems({ listView, active: false, createView: undefined })).toStrictEqual( + expect(getListItems(state)).toStrictEqual( createSampleTrustedApps(createDefaultPaginationInfo()) ); }); @@ -120,100 +150,239 @@ describe('selectors', () => { describe('getListTotalItemsCount()', () => { it('returns 0 when no valid data loaded', () => { - expect( - getListTotalItemsCount({ - listView: createDefaultListView(), - active: false, - createView: undefined, - }) - ).toBe(0); + const state = { ...initialState, listView: createDefaultListView(initialNow) }; + + expect(getListTotalItemsCount(state)).toBe(0); }); it('returns last loaded total items count', () => { - const listView = { - currentListResourceState: createListComplexLoadingResourceState( - createDefaultPaginationInfo(), - 200 - ), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListComplexLoadingResourceState( + createDefaultPaginationInfo(), + 200, + initialNow + ), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect(getListTotalItemsCount({ listView, active: false, createView: undefined })).toBe(200); + expect(getListTotalItemsCount(state)).toBe(200); }); }); describe('getListCurrentPageIndex()', () => { it('returns page index', () => { - expect( - getListCurrentPageIndex({ - listView: createDefaultListView(), - active: false, - createView: undefined, - }) - ).toBe(0); + const state = { ...initialState, listView: createDefaultListView(initialNow) }; + + expect(getListCurrentPageIndex(state)).toBe(0); }); }); describe('getListCurrentPageSize()', () => { - it('returns page index', () => { - expect( - getListCurrentPageSize({ - listView: createDefaultListView(), - active: false, - createView: undefined, - }) - ).toBe(20); + it('returns page size', () => { + const state = { ...initialState, listView: createDefaultListView(initialNow) }; + + expect(getListCurrentPageSize(state)).toBe(20); }); }); describe('getListErrorMessage()', () => { it('returns undefined when not in failed state', () => { - const listView = { - currentListResourceState: createListComplexLoadingResourceState( - createDefaultPaginationInfo(), - 200 - ), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListComplexLoadingResourceState( + createDefaultPaginationInfo(), + 200, + initialNow + ), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect( - getListErrorMessage({ listView, active: false, createView: undefined }) - ).toBeUndefined(); + expect(getListErrorMessage(state)).toBeUndefined(); }); it('returns message when not in failed state', () => { - const listView = { - currentListResourceState: createListFailedResourceState('Internal Server Error'), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListFailedResourceState('Internal Server Error'), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect(getListErrorMessage({ listView, active: false, createView: undefined })).toBe( - 'Internal Server Error' - ); + expect(getListErrorMessage(state)).toBe('Internal Server Error'); }); }); describe('isListLoading()', () => { it('returns false when no loading is happening', () => { - expect( - isListLoading({ listView: createDefaultListView(), active: false, createView: undefined }) - ).toBe(false); + expect(isListLoading(initialState)).toBe(false); }); it('returns true when loading is in progress', () => { - const listView = { - currentListResourceState: createListComplexLoadingResourceState( - createDefaultPaginationInfo(), - 200 - ), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListComplexLoadingResourceState( + createDefaultPaginationInfo(), + 200, + initialNow + ), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect(isListLoading({ listView, active: false, createView: undefined })).toBe(true); + expect(isListLoading(state)).toBe(true); + }); + }); + + describe('isDeletionDialogOpen()', () => { + it('returns false when no entry is set', () => { + expect(isDeletionDialogOpen(initialState)).toBe(false); + }); + + it('returns true when entry is set', () => { + const state = { + ...initialState, + deletionDialog: { + ...initialState.deletionDialog, + entry: createSampleTrustedApp(5), + }, + }; + + expect(isDeletionDialogOpen(state)).toBe(true); + }); + }); + + describe('isDeletionInProgress()', () => { + it('returns false when resource state is uninitialised', () => { + expect(isDeletionInProgress(initialState)).toBe(false); + }); + + it('returns true when resource state is loading', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }); + + expect(isDeletionInProgress(state)).toBe(true); + }); + + it('returns false when resource state is loaded', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadedResourceState', + data: null, + }); + + expect(isDeletionInProgress(state)).toBe(false); + }); + + it('returns false when resource state is failed', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'FailedResourceState', + error: createServerApiError('Not Found'), + }); + + expect(isDeletionInProgress(state)).toBe(false); + }); + }); + + describe('isDeletionSuccessful()', () => { + it('returns false when resource state is uninitialised', () => { + expect(isDeletionSuccessful(initialState)).toBe(false); + }); + + it('returns false when resource state is loading', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }); + + expect(isDeletionSuccessful(state)).toBe(false); + }); + + it('returns true when resource state is loaded', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadedResourceState', + data: null, + }); + + expect(isDeletionSuccessful(state)).toBe(true); + }); + + it('returns false when resource state is failed', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'FailedResourceState', + error: createServerApiError('Not Found'), + }); + + expect(isDeletionSuccessful(state)).toBe(false); + }); + }); + + describe('getDeletionError()', () => { + it('returns undefined when resource state is uninitialised', () => { + expect(getDeletionError(initialState)).toBeUndefined(); + }); + + it('returns undefined when resource state is loading', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }); + + expect(getDeletionError(state)).toBeUndefined(); + }); + + it('returns undefined when resource state is loaded', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadedResourceState', + data: null, + }); + + expect(getDeletionError(state)).toBeUndefined(); + }); + + it('returns error when resource state is failed', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'FailedResourceState', + error: createServerApiError('Not Found'), + }); + + expect(getDeletionError(state)).toStrictEqual(createServerApiError('Not Found')); + }); + }); + + describe('getDeletionSubmissionResourceState()', () => { + it('returns submission resource state', () => { + expect(getDeletionSubmissionResourceState(initialState)).toStrictEqual({ + type: 'UninitialisedResourceState', + }); + }); + }); + + describe('getDeletionDialogEntry()', () => { + it('returns undefined when no entry is set', () => { + expect(getDeletionDialogEntry(initialState)).toBeUndefined(); + }); + + it('returns entry when entry is set', () => { + const entry = createSampleTrustedApp(5); + const state = { ...initialState, deletionDialog: { ...initialState.deletionDialog, entry } }; + + expect(getDeletionDialogEntry(state)).toStrictEqual(entry); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts index f074b21f79f4e..6239b425efe2f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts @@ -5,12 +5,15 @@ */ import { createSelector } from 'reselect'; +import { ServerApiError } from '../../../../common/types'; import { Immutable, NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types'; import { AsyncResourceState, getCurrentResourceError, getLastLoadedResourceState, + isFailedResourceState, + isLoadedResourceState, isLoadingResourceState, isOutdatedResourceState, LoadedResourceState, @@ -32,12 +35,15 @@ const pageInfosEqual = (pageInfo1: PaginationInfo, pageInfo2: PaginationInfo): b export const needsRefreshOfListData = (state: Immutable): boolean => { const currentPageInfo = state.listView.currentPaginationInfo; const currentPage = state.listView.currentListResourceState; + const freshDataTimestamp = state.listView.freshDataTimestamp; return ( state.active && - isOutdatedResourceState(currentPage, (data) => - pageInfosEqual(currentPageInfo, data.paginationInfo) - ) + isOutdatedResourceState(currentPage, (data) => { + return ( + pageInfosEqual(currentPageInfo, data.paginationInfo) && data.timestamp >= freshDataTimestamp + ); + }) ); }; @@ -104,6 +110,38 @@ export const isListLoading = (state: Immutable): boole return isLoadingResourceState(state.listView.currentListResourceState); }; +export const isDeletionDialogOpen = (state: Immutable): boolean => { + return state.deletionDialog.entry !== undefined; +}; + +export const isDeletionInProgress = (state: Immutable): boolean => { + return isLoadingResourceState(state.deletionDialog.submissionResourceState); +}; + +export const isDeletionSuccessful = (state: Immutable): boolean => { + return isLoadedResourceState(state.deletionDialog.submissionResourceState); +}; + +export const getDeletionError = ( + state: Immutable +): Immutable | undefined => { + const submissionResourceState = state.deletionDialog.submissionResourceState; + + return isFailedResourceState(submissionResourceState) ? submissionResourceState.error : undefined; +}; + +export const getDeletionSubmissionResourceState = ( + state: Immutable +): AsyncResourceState => { + return state.deletionDialog.submissionResourceState; +}; + +export const getDeletionDialogEntry = ( + state: Immutable +): Immutable | undefined => { + return state.deletionDialog.entry; +}; + export const isCreatePending: (state: Immutable) => boolean = ({ createView, }) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts index 70e4e1e685b01..020a87f526e52 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts @@ -4,10 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import { combineReducers, createStore } from 'redux'; import { ServerApiError } from '../../../../common/types'; import { TrustedApp } from '../../../../../common/endpoint/types'; import { RoutingAction } from '../../../../common/store/routing'; +import { + MANAGEMENT_STORE_GLOBAL_NAMESPACE, + MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE, +} from '../../../common/constants'; + import { AsyncResourceState, FailedResourceState, @@ -20,30 +26,36 @@ import { UninitialisedResourceState, } from '../state'; +import { trustedAppsPageReducer } from '../store/reducer'; import { TrustedAppsListResourceStateChanged } from '../store/action'; -import { initialTrustedAppsPageState } from '../store/reducer'; const OS_LIST: Array = ['windows', 'macos', 'linux']; -export const createSampleTrustedApps = (paginationInfo: PaginationInfo): TrustedApp[] => { - return [...new Array(paginationInfo.size).keys()].map((i) => ({ - id: String(paginationInfo.index + i), - name: `trusted app ${paginationInfo.index + i}`, - description: `Trusted App ${paginationInfo.index + i}`, +export const createSampleTrustedApp = (i: number): TrustedApp => { + return { + id: String(i), + name: `trusted app ${i}`, + description: `Trusted App ${i}`, created_at: '1 minute ago', created_by: 'someone', os: OS_LIST[i % 3], entries: [], - })); + }; +}; + +export const createSampleTrustedApps = (paginationInfo: PaginationInfo): TrustedApp[] => { + return [...new Array(paginationInfo.size).keys()].map(createSampleTrustedApp); }; export const createTrustedAppsListData = ( paginationInfo: PaginationInfo, - totalItemsCount: number + totalItemsCount: number, + timestamp: number ) => ({ items: createSampleTrustedApps(paginationInfo), totalItemsCount, paginationInfo, + timestamp, }); export const createServerApiError = (message: string) => ({ @@ -58,10 +70,11 @@ export const createUninitialisedResourceState = (): UninitialisedResourceState = export const createListLoadedResourceState = ( paginationInfo: PaginationInfo, - totalItemsCount: number + totalItemsCount: number, + timestamp: number ): LoadedResourceState => ({ type: 'LoadedResourceState', - data: createTrustedAppsListData(paginationInfo, totalItemsCount), + data: createTrustedAppsListData(paginationInfo, totalItemsCount, timestamp), }); export const createListFailedResourceState = ( @@ -82,50 +95,64 @@ export const createListLoadingResourceState = ( export const createListComplexLoadingResourceState = ( paginationInfo: PaginationInfo, - totalItemsCount: number + totalItemsCount: number, + timestamp: number ): LoadingResourceState => createListLoadingResourceState( createListFailedResourceState( 'Internal Server Error', - createListLoadedResourceState(paginationInfo, totalItemsCount) + createListLoadedResourceState(paginationInfo, totalItemsCount, timestamp) ) ); export const createDefaultPaginationInfo = () => ({ index: 0, size: 20 }); -export const createDefaultListView = () => ({ - ...initialTrustedAppsPageState.listView, +export const createDefaultListView = ( + freshDataTimestamp: number +): TrustedAppsListPageState['listView'] => ({ currentListResourceState: createUninitialisedResourceState(), currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp, + show: undefined, }); export const createLoadingListViewWithPagination = ( + freshDataTimestamp: number, currentPaginationInfo: PaginationInfo, previousState: StaleResourceState = createUninitialisedResourceState() ): TrustedAppsListPageState['listView'] => ({ - ...initialTrustedAppsPageState.listView, currentListResourceState: { type: 'LoadingResourceState', previousState }, currentPaginationInfo, + freshDataTimestamp, + show: undefined, }); export const createLoadedListViewWithPagination = ( + freshDataTimestamp: number, paginationInfo: PaginationInfo = createDefaultPaginationInfo(), currentPaginationInfo: PaginationInfo = createDefaultPaginationInfo(), totalItemsCount: number = 200 ): TrustedAppsListPageState['listView'] => ({ - ...initialTrustedAppsPageState.listView, - currentListResourceState: createListLoadedResourceState(paginationInfo, totalItemsCount), + currentListResourceState: createListLoadedResourceState( + paginationInfo, + totalItemsCount, + freshDataTimestamp + ), currentPaginationInfo, + freshDataTimestamp, + show: undefined, }); export const createFailedListViewWithPagination = ( + freshDataTimestamp: number, currentPaginationInfo: PaginationInfo, error: ServerApiError, lastLoadedState?: LoadedResourceState ): TrustedAppsListPageState['listView'] => ({ - ...initialTrustedAppsPageState.listView, currentListResourceState: { type: 'FailedResourceState', error, lastLoadedState }, currentPaginationInfo, + freshDataTimestamp, + show: undefined, }); export const createUserChangedUrlAction = (path: string, search: string = ''): RoutingAction => { @@ -138,3 +165,13 @@ export const createTrustedAppsListResourceStateChangedAction = ( type: 'trustedAppsListResourceStateChanged', payload: { newState }, }); + +export const createGlobalNoMiddlewareStore = () => { + return createStore( + combineReducers({ + [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: combineReducers({ + [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: trustedAppsPageReducer, + }), + }) + ); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap new file mode 100644 index 0000000000000..fdb20f229f144 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap @@ -0,0 +1,315 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TrustedAppDeletionDialog renders correctly initially 1`] = ` + +
+ +`; + +exports[`TrustedAppDeletionDialog renders correctly when deletion failed 1`] = ` + +
+
+
+
+ +
+
+
+ Remove trusted application +
+
+
+
+
+

+ You are removing trusted application " + + trusted app 3 + + ". +

+

+ This action cannot be undone. Are you sure you wish to continue? +

+
+
+
+
+ + +
+
+
+
+
+ +`; + +exports[`TrustedAppDeletionDialog renders correctly when deletion is in progress 1`] = ` + +
+
+
+
+ +
+
+
+ Remove trusted application +
+
+
+
+
+

+ You are removing trusted application " + + trusted app 3 + + ". +

+

+ This action cannot be undone. Are you sure you wish to continue? +

+
+
+
+
+ + +
+
+
+
+
+ +`; + +exports[`TrustedAppDeletionDialog renders correctly when dialog started 1`] = ` + +
+
+
+
+ +
+
+
+ Remove trusted application +
+
+
+
+
+

+ You are removing trusted application " + + trusted app 3 + + ". +

+

+ This action cannot be undone. Are you sure you wish to continue? +

+
+
+
+
+ + +
+
+
+
+
+ +`; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap index e0f846f5950f7..ccd94c63e96c8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap @@ -4,6 +4,7 @@ exports[`TrustedAppsList renders correctly initially 1`] = `
+ +
+ + Actions + +
+ @@ -106,7 +123,7 @@ exports[`TrustedAppsList renders correctly initially 1`] = ` >
+ +
+ + Actions + +
+ @@ -232,7 +266,7 @@ exports[`TrustedAppsList renders correctly when failed loading data for the firs >
+ +
+ + Actions + +
+ @@ -363,7 +414,7 @@ exports[`TrustedAppsList renders correctly when failed loading data for the seco >
+ +
+ + Actions + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
- -
- Name + + + + Delete + + +
+ + + + +
+ Name
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ @@ -2080,6 +2748,7 @@ exports[`TrustedAppsList renders correctly when loading data for the first time
+ +
+ + Actions + +
+ @@ -2182,7 +2867,7 @@ exports[`TrustedAppsList renders correctly when loading data for the first time >
+ +
+ + Actions + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ - + +
+ + + + Delete + + +
+ + + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ @@ -3890,10 +5192,11 @@ exports[`TrustedAppsList renders correctly when loading data for the second time `; -exports[`TrustedAppsList renders correctly when new page and page sie set (not loading yet) 1`] = ` +exports[`TrustedAppsList renders correctly when new page and page size set (not loading yet) 1`] = `
+ +
+ + Actions + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap deleted file mode 100644 index c8d9b46d5a0d2..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap +++ /dev/null @@ -1,955 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TrustedAppsPage rendering 1`] = ` -Object { - "asFragment": [Function], - "baseElement": .c0 { - padding: 24px; -} - -.c0.siemWrapperPage--restrictWidthDefault, -.c0.siemWrapperPage--restrictWidthCustom { - box-sizing: content-box; - margin: 0 auto; -} - -.c0.siemWrapperPage--restrictWidthDefault { - max-width: 1000px; -} - -.c0.siemWrapperPage--fullHeight { - height: 100%; -} - -.c0.siemWrapperPage--withTimeline { - padding-right: 70px; -} - -.c0.siemWrapperPage--noPadding { - padding: 0; -} - -.c4 { - margin-top: 8px; -} - -.c4 .siemSubtitle__item { - color: #6a717d; - font-size: 12px; - line-height: 1.5; -} - -.c3 { - vertical-align: middle; -} - -.c1 { - margin-bottom: 24px; -} - -.c2 { - display: block; -} - -.c5 .euiFlyout { - z-index: 4001; -} - -@media only screen and (min-width:575px) { - .c4 .siemSubtitle__item { - display: inline-block; - margin-right: 16px; - } - - .c4 .siemSubtitle__item:last-child { - margin-right: 0; - } -} - - -
-
-
-
-
-

- Trusted Applications - - - Beta - -

-
-
- View and configure trusted applications -
-
-
-
- -
-
-
-
- - -
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - -
-
-
- - Name - -
-
-
- - OS - -
-
-
- - Date Created - -
-
-
- - Created By - -
-
-
- - No items found - -
-
-
-
-
-
-
- , - "container":
-
-
-
-
-

- Trusted Applications - - - Beta - -

-
-
- View and configure trusted applications -
-
-
-
- -
-
-
-
- - -
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - -
-
-
- - Name - -
-
-
- - OS - -
-
-
- - Date Created - -
-
-
- - Created By - -
-
-
- - No items found - -
-
-
-
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`TrustedAppsPage when the Add Trusted App button is clicked should display create form 1`] = ` -@media only screen and (min-width:575px) { - -} - -
-
-
- -
-
-
-
- -
-
-
-
-
-
- -
-
-
-
- -
-
- - Select an option: Windows, is selected - - -
- - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
- - Select an option: Hash, is selected - - -
- - -
-
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-