From af3ea4d2119b4e522956f511a8daafa5bce9c8c2 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 20 Jan 2021 13:37:37 +0100 Subject: [PATCH 01/83] add test to switch layer from regular index to rollup index (#88651) --- x-pack/test/functional/apps/lens/rollup.ts | 23 +++++++++++++++++- .../es_archives/lens/rollup/config/data.json | 24 ++++++++++++++++++- .../es_archives/lens/rollup/data/data.json | 12 ++++++++++ .../lens/rollup/data/mappings.json | 23 ++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/apps/lens/rollup.ts b/x-pack/test/functional/apps/lens/rollup.ts index f6882c8aed214..43be284a36228 100644 --- a/x-pack/test/functional/apps/lens/rollup.ts +++ b/x-pack/test/functional/apps/lens/rollup.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['visualize', 'lens']); + const PageObjects = getPageObjects(['visualize', 'lens', 'header']); const find = getService('find'); const listingTable = getService('listingTable'); const esArchiver = getService('esArchiver'); @@ -71,5 +71,26 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.lens.getDatatableHeaderText()).to.eql('Sum of bytes'); expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('16,788'); }); + + it('should allow to switch from regular index to rollup index retaining config', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.switchDataPanelIndexPattern('lens_regular_data'); + await PageObjects.lens.switchToVisualization('lnsMetric'); + await PageObjects.lens.configureDimension({ + dimension: 'lns-empty-dimension', + operation: 'sum', + field: 'bytes', + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await PageObjects.lens.assertMetric('Sum of bytes', '16,788'); + + await PageObjects.lens.switchFirstLayerIndexPattern('lens_rolled_up_data'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await PageObjects.lens.assertMetric('Sum of bytes', '16,788'); + }); }); } diff --git a/x-pack/test/functional/es_archives/lens/rollup/config/data.json b/x-pack/test/functional/es_archives/lens/rollup/config/data.json index 11645d0d7c575..cd57f4f352a7d 100644 --- a/x-pack/test/functional/es_archives/lens/rollup/config/data.json +++ b/x-pack/test/functional/es_archives/lens/rollup/config/data.json @@ -44,6 +44,28 @@ } } +{ + "type": "doc", + "value": { + "id": "index-pattern:lens-regular-data", + "index": ".kibana_1", + "source": { + "index-pattern" : { + "title" : "lens_regular_data", + "timeFieldName" : "@timestamp", + "fields" : "[]" + }, + "type" : "index-pattern", + "references" : [ ], + "migrationVersion" : { + "index-pattern" : "7.6.0" + }, + "updated_at" : "2020-08-19T08:39:09.998Z" + }, + "type": "_doc" + } +} + { "type": "doc", "value": { @@ -54,7 +76,7 @@ "accessibility:disableAnimations": true, "buildNum": 9007199254740991, "dateFormat:tz": "UTC", - "defaultIndex": "logstash-*" + "defaultIndex": "lens-rolled-up-data" }, "references": [], "type": "config", diff --git a/x-pack/test/functional/es_archives/lens/rollup/data/data.json b/x-pack/test/functional/es_archives/lens/rollup/data/data.json index 36dc10c05f0b9..ae28392b2b758 100644 --- a/x-pack/test/functional/es_archives/lens/rollup/data/data.json +++ b/x-pack/test/functional/es_archives/lens/rollup/data/data.json @@ -56,4 +56,16 @@ "_rollup.id": "lens_rolled_up_data" } } +} + +{ + "type": "doc", + "value": { + "index": "lens_regular_data", + "id": "1", + "source": { + "@timestamp": "2015-09-20T02:00:00.000Z", + "bytes": 16788 + } + } } \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/lens/rollup/data/mappings.json b/x-pack/test/functional/es_archives/lens/rollup/data/mappings.json index 0e47a632bbf3f..01b842ea031b7 100644 --- a/x-pack/test/functional/es_archives/lens/rollup/data/mappings.json +++ b/x-pack/test/functional/es_archives/lens/rollup/data/mappings.json @@ -1,3 +1,26 @@ +{ + "type": "index", + "value": { + "index": "lens_regular_data", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "bytes": { + "type": "float" + } + } + }, + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "0" + } + } + } +} + { "type": "index", "value": { From 55d4331a2b46ed24d3fef494d02a9807b8a38ca2 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 20 Jan 2021 13:27:37 +0000 Subject: [PATCH 02/83] skip flaky suite (#88796) --- .../apps/triggers_actions_ui/connectors.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index 1eb920a14a5e2..ab2270b4ce70f 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -17,7 +17,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const comboBox = getService('comboBox'); const supertest = getService('supertest'); - describe('Connectors', function () { + // FLAKY: https://github.com/elastic/kibana/issues/88796 + describe.skip('Connectors', function () { const objectRemover = new ObjectRemover(supertest); before(async () => { From 69182a8628ed832429c629bf57b257e3fe9f23eb Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 20 Jan 2021 13:47:08 +0000 Subject: [PATCH 03/83] chore(NA): create new x-pack cigroups and rebalancing them all (#88366) * chore(NA): create new x-pack cigroups and rebalancing them all * chore(NA): better cigroups balancing * chore(NA): push rollup tests back into ciGroup1 * chore(NA): move some functional ml tests from cigroup3 into cigroup13 * chore(NA): move some more tests into ciGroup13 * chore(NA): use a single top level describe at x-pack/test/functional/apps/ml * chore(NA): move settings into ciGroup13 * temporary test for es snapshots env * Revert "temporary test for es snapshots env" This reverts commit 789ebe7b9c1f87a05d1484e822a48420afd29e8f. * docs(NA): add missing documentation on the function tests describe split Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .ci/es-snapshots/Jenkinsfile_verify_es | 2 + .ci/jobs.yml | 2 + test/functional/apps/visualize/index.ts | 2 +- test/scripts/jenkins_xpack_build_kibana.sh | 4 +- vars/kibanaCoverage.groovy | 2 + vars/tasks.groovy | 2 +- .../basic/tests/index.ts | 2 +- .../spaces_only/tests/index.ts | 2 +- .../test/api_integration_basic/apis/index.ts | 2 +- .../exception_operators_data_types/index.ts | 36 ++++-- .../security_and_spaces/tests/index.ts | 61 +++++----- .../tests/index.ts | 2 +- .../apps/api_keys/feature_controls/index.ts | 2 +- .../feature_controls/index.ts | 2 +- .../test/functional/apps/dev_tools/index.ts | 2 +- x-pack/test/functional/apps/graph/index.ts | 2 +- .../functional/apps/grok_debugger/index.js | 2 +- .../functional/apps/index_management/index.ts | 2 +- .../functional/apps/ingest_pipelines/index.ts | 2 +- x-pack/test/functional/apps/ml/index.ts | 107 ++++++++++++------ x-pack/test/functional_cors/tests/index.ts | 2 +- x-pack/test/licensing_plugin/public/index.ts | 2 +- x-pack/test/licensing_plugin/server/index.ts | 2 +- .../test_suites/event_log/index.ts | 2 +- .../licensed_feature_usage/index.ts | 2 +- .../test_suites/platform/index.ts | 2 +- .../test_suites/task_manager/index.ts | 2 +- .../test_suites/task_manager/index.ts | 2 +- .../test_suites/global_search/index.ts | 2 +- .../reporting_without_security/index.ts | 2 +- .../api_integration/tagging_api/apis/index.ts | 2 +- .../tests/login_selector/index.ts | 2 +- .../security_functional/tests/oidc/index.ts | 2 +- .../security_functional/tests/saml/index.ts | 2 +- 34 files changed, 166 insertions(+), 102 deletions(-) diff --git a/.ci/es-snapshots/Jenkinsfile_verify_es b/.ci/es-snapshots/Jenkinsfile_verify_es index 3c38d6279a038..11a39faa9aed0 100644 --- a/.ci/es-snapshots/Jenkinsfile_verify_es +++ b/.ci/es-snapshots/Jenkinsfile_verify_es @@ -56,6 +56,8 @@ kibanaPipeline(timeoutMinutes: 150) { 'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9), 'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10), 'xpack-ciGroup11': kibanaPipeline.xpackCiGroupProcess(11), + 'xpack-ciGroup12': kibanaPipeline.xpackCiGroupProcess(12), + 'xpack-ciGroup13': kibanaPipeline.xpackCiGroupProcess(13), ]), ]) } diff --git a/.ci/jobs.yml b/.ci/jobs.yml index f62ec9510d2d4..b05e834f5a459 100644 --- a/.ci/jobs.yml +++ b/.ci/jobs.yml @@ -32,6 +32,8 @@ JOB: - x-pack-ciGroup9 - x-pack-ciGroup10 - x-pack-ciGroup11 + - x-pack-ciGroup12 + - x-pack-ciGroup13 - x-pack-accessibility - x-pack-visualRegression diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index ff249d9972525..dddcd82f1d3f8 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -86,7 +86,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { }); describe('', function () { - this.tags('ciGroup11'); + this.tags('ciGroup4'); loadTestFile(require.resolve('./_line_chart_split_series')); loadTestFile(require.resolve('./_line_chart_split_chart')); diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index 8bb6effbec89c..a9e603f63bd42 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -23,7 +23,9 @@ node scripts/functional_tests --assert-none-excluded \ --include-tag ciGroup8 \ --include-tag ciGroup9 \ --include-tag ciGroup10 \ - --include-tag ciGroup11 + --include-tag ciGroup11 \ + --include-tag ciGroup12 \ + --include-tag ciGroup13 # Do not build kibana for code coverage run if [[ -z "$CODE_COVERAGE" ]] ; then diff --git a/vars/kibanaCoverage.groovy b/vars/kibanaCoverage.groovy index 019eb8088dbfc..609d8f78aeb96 100644 --- a/vars/kibanaCoverage.groovy +++ b/vars/kibanaCoverage.groovy @@ -247,6 +247,8 @@ def xpackProks() { 'xpack-ciGroup9' : kibanaPipeline.xpackCiGroupProcess(9), 'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10), 'xpack-ciGroup11': kibanaPipeline.xpackCiGroupProcess(11), + 'xpack-ciGroup12': kibanaPipeline.xpackCiGroupProcess(12), + 'xpack-ciGroup13': kibanaPipeline.xpackCiGroupProcess(13), ] } diff --git a/vars/tasks.groovy b/vars/tasks.groovy index 68a6d4b32618c..3493a95f0bdce 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -94,7 +94,7 @@ def functionalXpack(Map params = [:]) { kibanaPipeline.buildXpack(10) if (config.ciGroups) { - def ciGroups = 1..11 + def ciGroups = 1..13 tasks(ciGroups.collect { kibanaPipeline.xpackCiGroupProcess(it) }) } diff --git a/x-pack/test/alerting_api_integration/basic/tests/index.ts b/x-pack/test/alerting_api_integration/basic/tests/index.ts index 80152cca07c60..5439379653327 100644 --- a/x-pack/test/alerting_api_integration/basic/tests/index.ts +++ b/x-pack/test/alerting_api_integration/basic/tests/index.ts @@ -12,7 +12,7 @@ export default function alertingApiIntegrationTests({ getService, }: FtrProviderContext) { describe('alerting api integration basic license', function () { - this.tags('ciGroup3'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./actions')); loadTestFile(require.resolve('./alerts')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts index 49227c2d5cdf8..7d2108c5436a1 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts @@ -10,7 +10,7 @@ import { Spaces } from '../scenarios'; // eslint-disable-next-line import/no-default-export export default function alertingApiIntegrationTests({ loadTestFile }: FtrProviderContext) { describe('alerting api integration spaces only', function () { - this.tags('ciGroup9'); + this.tags('ciGroup12'); loadTestFile(require.resolve('./actions')); loadTestFile(require.resolve('./alerting')); diff --git a/x-pack/test/api_integration_basic/apis/index.ts b/x-pack/test/api_integration_basic/apis/index.ts index 66b3a45c12df3..4c797b21c6fe1 100644 --- a/x-pack/test/api_integration_basic/apis/index.ts +++ b/x-pack/test/api_integration_basic/apis/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('apis', function () { - this.tags('ciGroup2'); + this.tags('ciGroup11'); loadTestFile(require.resolve('./ml')); loadTestFile(require.resolve('./transform')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts index 6b32eb19c83d9..78450ccd70c27 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/exception_operators_data_types/index.ts @@ -9,18 +9,30 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default ({ loadTestFile }: FtrProviderContext): void => { describe('Detection exceptions data types and operators', function () { - this.tags('ciGroup11'); + describe('', function () { + this.tags('ciGroup11'); - loadTestFile(require.resolve('./date')); - loadTestFile(require.resolve('./double')); - loadTestFile(require.resolve('./float')); - loadTestFile(require.resolve('./integer')); - loadTestFile(require.resolve('./ip')); - loadTestFile(require.resolve('./ip_array')); - loadTestFile(require.resolve('./keyword')); - loadTestFile(require.resolve('./keyword_array')); - loadTestFile(require.resolve('./long')); - loadTestFile(require.resolve('./text')); - loadTestFile(require.resolve('./text_array')); + loadTestFile(require.resolve('./date')); + loadTestFile(require.resolve('./double')); + loadTestFile(require.resolve('./float')); + loadTestFile(require.resolve('./integer')); + }); + + describe('', function () { + this.tags('ciGroup12'); + + loadTestFile(require.resolve('./ip')); + loadTestFile(require.resolve('./ip_array')); + loadTestFile(require.resolve('./keyword')); + loadTestFile(require.resolve('./keyword_array')); + loadTestFile(require.resolve('./long')); + }); + + describe('', function () { + this.tags('ciGroup13'); + + loadTestFile(require.resolve('./text')); + loadTestFile(require.resolve('./text_array')); + }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index 6eb74af910605..44e033e96c89b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts @@ -9,33 +9,40 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default ({ loadTestFile }: FtrProviderContext): void => { describe('detection engine api security and spaces enabled', function () { - this.tags('ciGroup11'); + describe('', function () { + this.tags('ciGroup11'); - loadTestFile(require.resolve('./add_actions')); - loadTestFile(require.resolve('./add_prepackaged_rules')); - loadTestFile(require.resolve('./create_rules')); - loadTestFile(require.resolve('./create_rules_bulk')); - loadTestFile(require.resolve('./create_threat_matching')); - loadTestFile(require.resolve('./create_exceptions')); - loadTestFile(require.resolve('./delete_rules')); - loadTestFile(require.resolve('./delete_rules_bulk')); - loadTestFile(require.resolve('./exception_operators_data_types/index')); - loadTestFile(require.resolve('./export_rules')); - loadTestFile(require.resolve('./find_rules')); - loadTestFile(require.resolve('./find_statuses')); - loadTestFile(require.resolve('./generating_signals')); - loadTestFile(require.resolve('./get_prepackaged_rules_status')); - loadTestFile(require.resolve('./import_rules')); - loadTestFile(require.resolve('./read_rules')); - loadTestFile(require.resolve('./update_rules')); - loadTestFile(require.resolve('./update_rules_bulk')); - loadTestFile(require.resolve('./patch_rules_bulk')); - loadTestFile(require.resolve('./patch_rules')); - loadTestFile(require.resolve('./query_signals')); - loadTestFile(require.resolve('./open_close_signals')); - loadTestFile(require.resolve('./get_signals_migration_status')); - loadTestFile(require.resolve('./create_signals_migrations')); - loadTestFile(require.resolve('./finalize_signals_migrations')); - loadTestFile(require.resolve('./delete_signals_migrations')); + loadTestFile(require.resolve('./add_actions')); + loadTestFile(require.resolve('./add_prepackaged_rules')); + loadTestFile(require.resolve('./create_rules')); + loadTestFile(require.resolve('./create_rules_bulk')); + loadTestFile(require.resolve('./create_threat_matching')); + loadTestFile(require.resolve('./create_exceptions')); + loadTestFile(require.resolve('./delete_rules')); + loadTestFile(require.resolve('./delete_rules_bulk')); + loadTestFile(require.resolve('./export_rules')); + loadTestFile(require.resolve('./find_rules')); + loadTestFile(require.resolve('./find_statuses')); + loadTestFile(require.resolve('./generating_signals')); + loadTestFile(require.resolve('./get_prepackaged_rules_status')); + loadTestFile(require.resolve('./import_rules')); + loadTestFile(require.resolve('./read_rules')); + loadTestFile(require.resolve('./update_rules')); + loadTestFile(require.resolve('./update_rules_bulk')); + loadTestFile(require.resolve('./patch_rules_bulk')); + loadTestFile(require.resolve('./patch_rules')); + loadTestFile(require.resolve('./query_signals')); + loadTestFile(require.resolve('./open_close_signals')); + loadTestFile(require.resolve('./get_signals_migration_status')); + loadTestFile(require.resolve('./create_signals_migrations')); + loadTestFile(require.resolve('./finalize_signals_migrations')); + loadTestFile(require.resolve('./delete_signals_migrations')); + }); + + // That split here enable us on using a different ciGroup to run the tests + // listed on ./exception_operators_data_types/index + describe('', function () { + loadTestFile(require.resolve('./exception_operators_data_types/index')); + }); }); }; diff --git a/x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts b/x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts index 4948bee7e6f5e..83c4e5a48b89e 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('encryptedSavedObjects', function encryptedSavedObjectsSuite() { - this.tags('ciGroup2'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./encrypted_saved_objects_api')); }); } diff --git a/x-pack/test/functional/apps/api_keys/feature_controls/index.ts b/x-pack/test/functional/apps/api_keys/feature_controls/index.ts index 169b5c7fb0a73..4cd95e5966bb5 100644 --- a/x-pack/test/functional/apps/api_keys/feature_controls/index.ts +++ b/x-pack/test/functional/apps/api_keys/feature_controls/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('feature controls', function () { - this.tags(['ciGroup2']); + this.tags(['ciGroup8']); loadTestFile(require.resolve('./api_keys_security')); }); diff --git a/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/index.ts b/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/index.ts index e7be2cb48ce3e..89690829a0d35 100644 --- a/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/index.ts +++ b/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('feature controls', function () { - this.tags(['ciGroup2']); + this.tags(['ciGroup8']); loadTestFile(require.resolve('./ccr_security')); }); diff --git a/x-pack/test/functional/apps/dev_tools/index.ts b/x-pack/test/functional/apps/dev_tools/index.ts index 767829d4494b7..121d9effc2b6f 100644 --- a/x-pack/test/functional/apps/dev_tools/index.ts +++ b/x-pack/test/functional/apps/dev_tools/index.ts @@ -7,7 +7,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('console', function () { - this.tags('ciGroup10'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./searchprofiler_editor')); diff --git a/x-pack/test/functional/apps/graph/index.ts b/x-pack/test/functional/apps/graph/index.ts index 4eabb0701d933..00f2295c52728 100644 --- a/x-pack/test/functional/apps/graph/index.ts +++ b/x-pack/test/functional/apps/graph/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('graph app', function () { - this.tags('ciGroup1'); + this.tags('ciGroup12'); loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./graph')); diff --git a/x-pack/test/functional/apps/grok_debugger/index.js b/x-pack/test/functional/apps/grok_debugger/index.js index 75c05f35abd28..948509f650e97 100644 --- a/x-pack/test/functional/apps/grok_debugger/index.js +++ b/x-pack/test/functional/apps/grok_debugger/index.js @@ -6,7 +6,7 @@ export default function ({ loadTestFile }) { describe('logstash', function () { - this.tags('ciGroup2'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./grok_debugger')); }); diff --git a/x-pack/test/functional/apps/index_management/index.ts b/x-pack/test/functional/apps/index_management/index.ts index 97b23cbf82c31..4b585b4d698dd 100644 --- a/x-pack/test/functional/apps/index_management/index.ts +++ b/x-pack/test/functional/apps/index_management/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext) => { describe('Index Management app', function () { - this.tags('ciGroup3'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./home_page')); }); diff --git a/x-pack/test/functional/apps/ingest_pipelines/index.ts b/x-pack/test/functional/apps/ingest_pipelines/index.ts index 2a4781c5e216d..b02f23245d8ac 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/index.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext) => { describe('Ingest pipelines app', function () { - this.tags('ciGroup3'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./ingest_pipelines')); }); diff --git a/x-pack/test/functional/apps/ml/index.ts b/x-pack/test/functional/apps/ml/index.ts index 29e852f96eea0..7956965be78f6 100644 --- a/x-pack/test/functional/apps/ml/index.ts +++ b/x-pack/test/functional/apps/ml/index.ts @@ -10,44 +10,81 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const ml = getService('ml'); describe('machine learning', function () { - this.tags('ciGroup3'); + describe('', function () { + this.tags('ciGroup3'); - before(async () => { - await ml.securityCommon.createMlRoles(); - await ml.securityCommon.createMlUsers(); - }); + before(async () => { + await ml.securityCommon.createMlRoles(); + await ml.securityCommon.createMlUsers(); + }); + + after(async () => { + await ml.securityCommon.cleanMlUsers(); + await ml.securityCommon.cleanMlRoles(); + await ml.testResources.deleteSavedSearches(); + await ml.testResources.deleteDashboards(); + await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + await ml.testResources.deleteIndexPatternByTitle('ft_ecommerce'); + await ml.testResources.deleteIndexPatternByTitle('ft_categorization'); + await ml.testResources.deleteIndexPatternByTitle('ft_event_rate_gen_trend_nanos'); + await ml.testResources.deleteIndexPatternByTitle('ft_bank_marketing'); + await ml.testResources.deleteIndexPatternByTitle('ft_ihp_outlier'); + await ml.testResources.deleteIndexPatternByTitle('ft_egs_regression'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); + await esArchiver.unload('ml/farequote'); + await esArchiver.unload('ml/ecommerce'); + await esArchiver.unload('ml/categorization'); + await esArchiver.unload('ml/event_rate_nanos'); + await esArchiver.unload('ml/bm_classification'); + await esArchiver.unload('ml/ihp_outlier'); + await esArchiver.unload('ml/egs_regression'); + await esArchiver.unload('ml/module_sample_ecommerce'); + await ml.testResources.resetKibanaTimeZone(); + await ml.securityUI.logout(); + }); - after(async () => { - await ml.securityCommon.cleanMlUsers(); - await ml.securityCommon.cleanMlRoles(); - await ml.testResources.deleteSavedSearches(); - await ml.testResources.deleteDashboards(); - await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); - await ml.testResources.deleteIndexPatternByTitle('ft_ecommerce'); - await ml.testResources.deleteIndexPatternByTitle('ft_categorization'); - await ml.testResources.deleteIndexPatternByTitle('ft_event_rate_gen_trend_nanos'); - await ml.testResources.deleteIndexPatternByTitle('ft_bank_marketing'); - await ml.testResources.deleteIndexPatternByTitle('ft_ihp_outlier'); - await ml.testResources.deleteIndexPatternByTitle('ft_egs_regression'); - await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); - await esArchiver.unload('ml/farequote'); - await esArchiver.unload('ml/ecommerce'); - await esArchiver.unload('ml/categorization'); - await esArchiver.unload('ml/event_rate_nanos'); - await esArchiver.unload('ml/bm_classification'); - await esArchiver.unload('ml/ihp_outlier'); - await esArchiver.unload('ml/egs_regression'); - await esArchiver.unload('ml/module_sample_ecommerce'); - await ml.testResources.resetKibanaTimeZone(); - await ml.securityUI.logout(); + loadTestFile(require.resolve('./permissions')); + loadTestFile(require.resolve('./pages')); + loadTestFile(require.resolve('./anomaly_detection')); + loadTestFile(require.resolve('./data_visualizer')); + loadTestFile(require.resolve('./data_frame_analytics')); }); - loadTestFile(require.resolve('./feature_controls')); - loadTestFile(require.resolve('./permissions')); - loadTestFile(require.resolve('./pages')); - loadTestFile(require.resolve('./anomaly_detection')); - loadTestFile(require.resolve('./data_visualizer')); - loadTestFile(require.resolve('./data_frame_analytics')); - loadTestFile(require.resolve('./settings')); + describe('', function () { + this.tags('ciGroup13'); + + before(async () => { + await ml.securityCommon.createMlRoles(); + await ml.securityCommon.createMlUsers(); + }); + + after(async () => { + await ml.securityCommon.cleanMlUsers(); + await ml.securityCommon.cleanMlRoles(); + await ml.testResources.deleteSavedSearches(); + await ml.testResources.deleteDashboards(); + await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + await ml.testResources.deleteIndexPatternByTitle('ft_ecommerce'); + await ml.testResources.deleteIndexPatternByTitle('ft_categorization'); + await ml.testResources.deleteIndexPatternByTitle('ft_event_rate_gen_trend_nanos'); + await ml.testResources.deleteIndexPatternByTitle('ft_bank_marketing'); + await ml.testResources.deleteIndexPatternByTitle('ft_ihp_outlier'); + await ml.testResources.deleteIndexPatternByTitle('ft_egs_regression'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); + await esArchiver.unload('ml/farequote'); + await esArchiver.unload('ml/ecommerce'); + await esArchiver.unload('ml/categorization'); + await esArchiver.unload('ml/event_rate_nanos'); + await esArchiver.unload('ml/bm_classification'); + await esArchiver.unload('ml/ihp_outlier'); + await esArchiver.unload('ml/egs_regression'); + await esArchiver.unload('ml/module_sample_ecommerce'); + await ml.testResources.resetKibanaTimeZone(); + await ml.securityUI.logout(); + }); + + loadTestFile(require.resolve('./feature_controls')); + loadTestFile(require.resolve('./settings')); + }); }); } diff --git a/x-pack/test/functional_cors/tests/index.ts b/x-pack/test/functional_cors/tests/index.ts index 7e16e1339b1e7..673cb464c860d 100644 --- a/x-pack/test/functional_cors/tests/index.ts +++ b/x-pack/test/functional_cors/tests/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Kibana cors', function () { - this.tags('ciGroup2'); + this.tags('ciGroup12'); loadTestFile(require.resolve('./cors')); }); } diff --git a/x-pack/test/licensing_plugin/public/index.ts b/x-pack/test/licensing_plugin/public/index.ts index 268a74c56bd72..e771098ecd36a 100644 --- a/x-pack/test/licensing_plugin/public/index.ts +++ b/x-pack/test/licensing_plugin/public/index.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../services'; // eslint-disable-next-line import/no-default-export export default function ({ loadTestFile }: FtrProviderContext) { describe('Licensing plugin public client', function () { - this.tags('ciGroup2'); + this.tags('ciGroup5'); loadTestFile(require.resolve('./feature_usage')); // MUST BE LAST! CHANGES LICENSE TYPE! loadTestFile(require.resolve('./updates')); diff --git a/x-pack/test/licensing_plugin/server/index.ts b/x-pack/test/licensing_plugin/server/index.ts index dcea6e042443e..a9613e6569703 100644 --- a/x-pack/test/licensing_plugin/server/index.ts +++ b/x-pack/test/licensing_plugin/server/index.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../services'; // eslint-disable-next-line import/no-default-export export default function ({ loadTestFile }: FtrProviderContext) { describe('Licensing plugin server client', function () { - this.tags('ciGroup2'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./info')); loadTestFile(require.resolve('./header')); diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/index.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/index.ts index c738205361207..4965e1aa7d2dd 100644 --- a/x-pack/test/plugin_api_integration/test_suites/event_log/index.ts +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('event_log', function taskManagerSuite() { - this.tags('ciGroup2'); + this.tags('ciGroup6'); loadTestFile(require.resolve('./public_api_integration')); loadTestFile(require.resolve('./service_api_integration')); }); diff --git a/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/index.ts b/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/index.ts index 2687c06351d69..23127f4db3d50 100644 --- a/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/index.ts +++ b/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Licensed feature usage APIs', function () { - this.tags('ciGroup2'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./feature_usage')); }); } diff --git a/x-pack/test/plugin_api_integration/test_suites/platform/index.ts b/x-pack/test/plugin_api_integration/test_suites/platform/index.ts index 3375e9ca839a5..82308b6168ac1 100644 --- a/x-pack/test/plugin_api_integration/test_suites/platform/index.ts +++ b/x-pack/test/plugin_api_integration/test_suites/platform/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('platform', function taskManagerSuite() { - this.tags('ciGroup2'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./elasticsearch_client')); }); } diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts index c1e7aad8ac36f..69fbb6be3ad59 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('task_manager', function taskManagerSuite() { - this.tags('ciGroup2'); + this.tags('ciGroup12'); loadTestFile(require.resolve('./health_route')); loadTestFile(require.resolve('./task_management')); loadTestFile(require.resolve('./task_management_removed_types')); diff --git a/x-pack/test/plugin_api_perf/test_suites/task_manager/index.ts b/x-pack/test/plugin_api_perf/test_suites/task_manager/index.ts index 648890a2b5431..302ddf071bc3d 100644 --- a/x-pack/test/plugin_api_perf/test_suites/task_manager/index.ts +++ b/x-pack/test/plugin_api_perf/test_suites/task_manager/index.ts @@ -14,7 +14,7 @@ export default function ({ loadTestFile }: { loadTestFile: (file: string) => voi * worth keeping around for future use, rather than being rewritten time and time again. */ describe.skip('task_manager_perf', function taskManagerSuite() { - this.tags('ciGroup2'); + this.tags('ciGroup12'); loadTestFile(require.resolve('./task_manager_perf_integration')); }); } diff --git a/x-pack/test/plugin_functional/test_suites/global_search/index.ts b/x-pack/test/plugin_functional/test_suites/global_search/index.ts index f3557ee8cc8db..f05aebd26cc86 100644 --- a/x-pack/test/plugin_functional/test_suites/global_search/index.ts +++ b/x-pack/test/plugin_functional/test_suites/global_search/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('GlobalSearch API', function () { - this.tags('ciGroup10'); + this.tags('ciGroup7'); loadTestFile(require.resolve('./global_search_providers')); loadTestFile(require.resolve('./global_search_bar')); }); diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/index.ts b/x-pack/test/reporting_api_integration/reporting_without_security/index.ts index 12b32f0f6c4c6..423fed069ee04 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/index.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ loadTestFile }: FtrProviderContext) { describe('Reporting APIs', function () { - this.tags('ciGroup2'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./job_apis')); loadTestFile(require.resolve('./management')); }); diff --git a/x-pack/test/saved_object_tagging/api_integration/tagging_api/apis/index.ts b/x-pack/test/saved_object_tagging/api_integration/tagging_api/apis/index.ts index fd9720cc453e5..51bfa8d976c92 100644 --- a/x-pack/test/saved_object_tagging/api_integration/tagging_api/apis/index.ts +++ b/x-pack/test/saved_object_tagging/api_integration/tagging_api/apis/index.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../services'; // eslint-disable-next-line import/no-default-export export default function ({ loadTestFile }: FtrProviderContext) { describe('saved objects tagging API', function () { - this.tags('ciGroup10'); + this.tags('ciGroup12'); loadTestFile(require.resolve('./delete')); loadTestFile(require.resolve('./create')); diff --git a/x-pack/test/security_functional/tests/login_selector/index.ts b/x-pack/test/security_functional/tests/login_selector/index.ts index ee25e365d495d..f07a380989e9f 100644 --- a/x-pack/test/security_functional/tests/login_selector/index.ts +++ b/x-pack/test/security_functional/tests/login_selector/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security app - login selector', function () { - this.tags('ciGroup4'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./basic_functionality')); loadTestFile(require.resolve('./auth_provider_hint')); diff --git a/x-pack/test/security_functional/tests/oidc/index.ts b/x-pack/test/security_functional/tests/oidc/index.ts index 2b6e433409fb4..9c19f25854afb 100644 --- a/x-pack/test/security_functional/tests/oidc/index.ts +++ b/x-pack/test/security_functional/tests/oidc/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security app - OIDC interactions', function () { - this.tags('ciGroup4'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./url_capture')); }); diff --git a/x-pack/test/security_functional/tests/saml/index.ts b/x-pack/test/security_functional/tests/saml/index.ts index 4b3d6a925bf76..cc8a6ce93956c 100644 --- a/x-pack/test/security_functional/tests/saml/index.ts +++ b/x-pack/test/security_functional/tests/saml/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security app - SAML interactions', function () { - this.tags('ciGroup4'); + this.tags('ciGroup13'); loadTestFile(require.resolve('./url_capture')); }); From ad8589eb1911eb8918420194cefc85de8173b603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 20 Jan 2021 13:56:40 +0000 Subject: [PATCH 04/83] [Telemetry] Handle Promise Rejection (#88815) --- src/plugins/telemetry/server/fetcher.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index f99a769b58d32..9e1499bbb37c0 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -169,6 +169,9 @@ export class FetcherTask { updateTelemetrySavedObject(this.internalRepository!, { reportFailureCount: 0, lastReported: this.lastReported, + }).catch((err) => { + err.message = `Failed to update the telemetry saved object: ${err.message}`; + this.logger.debug(err); }); } @@ -176,6 +179,9 @@ export class FetcherTask { updateTelemetrySavedObject(this.internalRepository!, { reportFailureCount: failureCount + 1, reportFailureVersion: this.currentKibanaVersion, + }).catch((err) => { + err.message = `Failed to update the telemetry saved object: ${err.message}`; + this.logger.debug(err); }); } From ee5f560dd7ebc44b326f9798e24f3d05df8929c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 20 Jan 2021 15:30:35 +0000 Subject: [PATCH 05/83] [FTR] Use new ES client instead of legacyEs service (#88720) --- .../apis/home/{index.js => index.ts} | 4 +- .../home/{sample_data.js => sample_data.ts} | 11 ++--- .../apis/{index.js => index.ts} | 4 +- .../apis/kql_telemetry/{index.js => index.ts} | 4 +- .../{kql_telemetry.js => kql_telemetry.ts} | 9 ++-- .../{bulk_create.js => bulk_create.ts} | 13 +++--- .../{bulk_get.js => bulk_get.ts} | 10 ++--- .../{bulk_update.js => bulk_update.ts} | 10 ++--- .../saved_objects/{create.js => create.ts} | 12 +++--- .../saved_objects/{delete.js => delete.ts} | 10 ++--- .../apis/saved_objects/export.ts | 7 +--- .../apis/saved_objects/{find.js => find.ts} | 42 ++++++++++--------- .../apis/saved_objects/{get.js => get.ts} | 10 ++--- .../apis/saved_objects/{index.js => index.ts} | 4 +- ...ort_errors.js => resolve_import_errors.ts} | 3 +- .../saved_objects/{update.js => update.ts} | 10 ++--- .../apis/saved_objects_management/find.ts | 7 +--- .../apis/saved_objects_management/get.ts | 7 +--- .../{ui_counters.js => ui_counters.ts} | 0 ...{telemetry_local.js => telemetry_local.ts} | 25 +++++++---- .../telemetry/telemetry_optin_notice_seen.ts | 27 +++++------- .../apis/ui_counters/{index.js => index.ts} | 4 +- .../{ui_counters.js => ui_counters.ts} | 26 +++++++----- .../apis/ui_metric/{index.js => index.ts} | 4 +- .../ui_metric/{ui_metric.js => ui_metric.ts} | 33 +++++++++------ .../telemetry/telemetry_optin_notice_seen.ts | 27 +++++------- 26 files changed, 165 insertions(+), 158 deletions(-) rename test/api_integration/apis/home/{index.js => index.ts} (77%) rename test/api_integration/apis/home/{sample_data.js => sample_data.ts} (90%) rename test/api_integration/apis/{index.js => index.ts} (90%) rename test/api_integration/apis/kql_telemetry/{index.js => index.ts} (76%) rename test/api_integration/apis/kql_telemetry/{kql_telemetry.js => kql_telemetry.ts} (93%) rename test/api_integration/apis/saved_objects/{bulk_create.js => bulk_create.ts} (93%) rename test/api_integration/apis/saved_objects/{bulk_get.js => bulk_get.ts} (95%) rename test/api_integration/apis/saved_objects/{bulk_update.js => bulk_update.ts} (97%) rename test/api_integration/apis/saved_objects/{create.js => create.ts} (90%) rename test/api_integration/apis/saved_objects/{delete.js => delete.ts} (89%) rename test/api_integration/apis/saved_objects/{find.js => find.ts} (93%) rename test/api_integration/apis/saved_objects/{get.js => get.ts} (93%) rename test/api_integration/apis/saved_objects/{index.js => index.ts} (88%) rename test/api_integration/apis/saved_objects/{resolve_import_errors.js => resolve_import_errors.ts} (98%) rename test/api_integration/apis/saved_objects/{update.js => update.ts} (95%) rename test/api_integration/apis/telemetry/__fixtures__/{ui_counters.js => ui_counters.ts} (100%) rename test/api_integration/apis/telemetry/{telemetry_local.js => telemetry_local.ts} (92%) rename test/api_integration/apis/ui_counters/{index.js => index.ts} (77%) rename test/api_integration/apis/ui_counters/{ui_counters.js => ui_counters.ts} (71%) rename test/api_integration/apis/ui_metric/{index.js => index.ts} (76%) rename test/api_integration/apis/ui_metric/{ui_metric.js => ui_metric.ts} (74%) diff --git a/test/api_integration/apis/home/index.js b/test/api_integration/apis/home/index.ts similarity index 77% rename from test/api_integration/apis/home/index.js rename to test/api_integration/apis/home/index.ts index 51b599cdd0816..95ff370456866 100644 --- a/test/api_integration/apis/home/index.js +++ b/test/api_integration/apis/home/index.ts @@ -6,7 +6,9 @@ * Public License, v 1. */ -export default function ({ loadTestFile }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('home apis', () => { loadTestFile(require.resolve('./sample_data')); }); diff --git a/test/api_integration/apis/home/sample_data.js b/test/api_integration/apis/home/sample_data.ts similarity index 90% rename from test/api_integration/apis/home/sample_data.js rename to test/api_integration/apis/home/sample_data.ts index bac1ef11f1070..042aff1375267 100644 --- a/test/api_integration/apis/home/sample_data.js +++ b/test/api_integration/apis/home/sample_data.ts @@ -7,10 +7,11 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const MILLISECOND_IN_WEEK = 1000 * 60 * 60 * 24 * 7; @@ -43,7 +44,7 @@ export default function ({ getService }) { }); it('should load elasticsearch index containing sample data with dates relative to current time', async () => { - const resp = await es.search({ + const { body: resp } = await es.search({ index: 'kibana_sample_data_flights', }); @@ -61,7 +62,7 @@ export default function ({ getService }) { .post(`/api/sample_data/flights?now=${nowString}`) .set('kbn-xsrf', 'kibana'); - const resp = await es.search({ + const { body: resp } = await es.search({ index: 'kibana_sample_data_flights', }); @@ -80,7 +81,7 @@ export default function ({ getService }) { }); it('should remove elasticsearch index containing sample data', async () => { - const resp = await es.indices.exists({ + const { body: resp } = await es.indices.exists({ index: 'kibana_sample_data_flights', }); expect(resp).to.be(false); diff --git a/test/api_integration/apis/index.js b/test/api_integration/apis/index.ts similarity index 90% rename from test/api_integration/apis/index.js rename to test/api_integration/apis/index.ts index 25f46feab693a..73629231b5951 100644 --- a/test/api_integration/apis/index.js +++ b/test/api_integration/apis/index.ts @@ -6,7 +6,9 @@ * Public License, v 1. */ -export default function ({ loadTestFile }) { +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('apis', () => { loadTestFile(require.resolve('./core')); loadTestFile(require.resolve('./general')); diff --git a/test/api_integration/apis/kql_telemetry/index.js b/test/api_integration/apis/kql_telemetry/index.ts similarity index 76% rename from test/api_integration/apis/kql_telemetry/index.js rename to test/api_integration/apis/kql_telemetry/index.ts index 18e2fa680c432..afcb96ea8981c 100644 --- a/test/api_integration/apis/kql_telemetry/index.js +++ b/test/api_integration/apis/kql_telemetry/index.ts @@ -6,7 +6,9 @@ * Public License, v 1. */ -export default function ({ loadTestFile }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('KQL', () => { loadTestFile(require.resolve('./kql_telemetry')); }); diff --git a/test/api_integration/apis/kql_telemetry/kql_telemetry.js b/test/api_integration/apis/kql_telemetry/kql_telemetry.ts similarity index 93% rename from test/api_integration/apis/kql_telemetry/kql_telemetry.js rename to test/api_integration/apis/kql_telemetry/kql_telemetry.ts index 1963b4edebb66..027206b971c45 100644 --- a/test/api_integration/apis/kql_telemetry/kql_telemetry.js +++ b/test/api_integration/apis/kql_telemetry/kql_telemetry.ts @@ -9,11 +9,12 @@ import expect from '@kbn/expect'; import Bluebird from 'bluebird'; import { get } from 'lodash'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('legacyEs'); + const es = getService('es'); describe('telemetry API', () => { before(() => esArchiver.load('saved_objects/basic')); @@ -31,7 +32,7 @@ export default function ({ getService }) { index: '.kibana', q: 'type:kql-telemetry', }) - .then((response) => { + .then(({ body: response }) => { const kqlTelemetryDoc = get(response, 'hits.hits[0]._source.kql-telemetry'); expect(kqlTelemetryDoc.optInCount).to.be(1); }); @@ -49,7 +50,7 @@ export default function ({ getService }) { index: '.kibana', q: 'type:kql-telemetry', }) - .then((response) => { + .then(({ body: response }) => { const kqlTelemetryDoc = get(response, 'hits.hits[0]._source.kql-telemetry'); expect(kqlTelemetryDoc.optOutCount).to.be(1); }); diff --git a/test/api_integration/apis/saved_objects/bulk_create.js b/test/api_integration/apis/saved_objects/bulk_create.ts similarity index 93% rename from test/api_integration/apis/saved_objects/bulk_create.js rename to test/api_integration/apis/saved_objects/bulk_create.ts index 2c18df8bc3b19..903332a0a930f 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.js +++ b/test/api_integration/apis/saved_objects/bulk_create.ts @@ -7,10 +7,11 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const esArchiver = getService('esArchiver'); const BULK_REQUESTS = [ @@ -74,11 +75,10 @@ export default function ({ getService }) { it('should not return raw id when object id is unspecified', async () => await supertest .post(`/api/saved_objects/_bulk_create`) - // eslint-disable-next-line no-unused-vars .send(BULK_REQUESTS.map(({ id, ...rest }) => rest)) .expect(200) .then((resp) => { - resp.body.saved_objects.map(({ id }) => + resp.body.saved_objects.map(({ id }: { id: string }) => expect(id).not.match(/visualization|dashboard/) ); })); @@ -88,10 +88,7 @@ export default function ({ getService }) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return 200 with individual responses', async () => diff --git a/test/api_integration/apis/saved_objects/bulk_get.js b/test/api_integration/apis/saved_objects/bulk_get.ts similarity index 95% rename from test/api_integration/apis/saved_objects/bulk_get.js rename to test/api_integration/apis/saved_objects/bulk_get.ts index 7870f880b5f74..e552c08a58cf0 100644 --- a/test/api_integration/apis/saved_objects/bulk_get.js +++ b/test/api_integration/apis/saved_objects/bulk_get.ts @@ -7,10 +7,11 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const esArchiver = getService('esArchiver'); const BULK_REQUESTS = [ @@ -98,10 +99,7 @@ export default function ({ getService }) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return 200 with individual responses', async () => diff --git a/test/api_integration/apis/saved_objects/bulk_update.js b/test/api_integration/apis/saved_objects/bulk_update.ts similarity index 97% rename from test/api_integration/apis/saved_objects/bulk_update.js rename to test/api_integration/apis/saved_objects/bulk_update.ts index b7e48a467c26a..5a2496b6dde81 100644 --- a/test/api_integration/apis/saved_objects/bulk_update.js +++ b/test/api_integration/apis/saved_objects/bulk_update.ts @@ -8,10 +8,11 @@ import expect from '@kbn/expect'; import _ from 'lodash'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const esArchiver = getService('esArchiver'); describe('bulkUpdate', () => { @@ -234,10 +235,7 @@ export default function ({ getService }) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return generic 404', async () => { diff --git a/test/api_integration/apis/saved_objects/create.js b/test/api_integration/apis/saved_objects/create.ts similarity index 90% rename from test/api_integration/apis/saved_objects/create.js rename to test/api_integration/apis/saved_objects/create.ts index 06e3b68121a26..b1cd5a8dfdae4 100644 --- a/test/api_integration/apis/saved_objects/create.js +++ b/test/api_integration/apis/saved_objects/create.ts @@ -7,10 +7,11 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const esArchiver = getService('esArchiver'); describe('create', () => { @@ -58,10 +59,7 @@ export default function ({ getService }) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return 200 and create kibana index', async () => { @@ -99,7 +97,7 @@ export default function ({ getService }) { expect(resp.body.migrationVersion).to.be.ok(); }); - expect(await es.indices.exists({ index: '.kibana' })).to.be(true); + expect((await es.indices.exists({ index: '.kibana' })).body).to.be(true); }); }); }); diff --git a/test/api_integration/apis/saved_objects/delete.js b/test/api_integration/apis/saved_objects/delete.ts similarity index 89% rename from test/api_integration/apis/saved_objects/delete.js rename to test/api_integration/apis/saved_objects/delete.ts index f78080dc62b30..9ba51b4b91468 100644 --- a/test/api_integration/apis/saved_objects/delete.js +++ b/test/api_integration/apis/saved_objects/delete.ts @@ -7,10 +7,11 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const esArchiver = getService('esArchiver'); describe('delete', () => { @@ -43,10 +44,7 @@ export default function ({ getService }) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('returns generic 404 when kibana index is missing', async () => diff --git a/test/api_integration/apis/saved_objects/export.ts b/test/api_integration/apis/saved_objects/export.ts index 59847906e6165..a84f3050fdd17 100644 --- a/test/api_integration/apis/saved_objects/export.ts +++ b/test/api_integration/apis/saved_objects/export.ts @@ -14,7 +14,7 @@ function ndjsonToObject(input: string) { } export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const esArchiver = getService('esArchiver'); describe('export', () => { @@ -524,10 +524,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return empty response', async () => { diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.ts similarity index 93% rename from test/api_integration/apis/saved_objects/find.js rename to test/api_integration/apis/saved_objects/find.ts index a6dc1645dfbc9..a3ce70888049c 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.ts @@ -7,10 +7,12 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { SavedObject } from '../../../../src/core/server'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const esArchiver = getService('esArchiver'); describe('find', () => { @@ -256,7 +258,6 @@ export default function ({ getService }) { ) .expect(400) .then((resp) => { - console.log('body', JSON.stringify(resp.body)); expect(resp.body).to.eql({ error: 'Bad Request', message: 'This type dashboard is not allowed: Bad Request', @@ -271,7 +272,6 @@ export default function ({ getService }) { ) .expect(400) .then((resp) => { - console.log('body', JSON.stringify(resp.body)); expect(resp.body).to.eql({ error: 'Bad Request', message: @@ -297,7 +297,10 @@ export default function ({ getService }) { .expect(200) .then((resp) => { const objects = resp.body.saved_objects; - expect(objects.map((obj) => obj.id)).to.eql(['only-ref-1', 'ref-1-and-ref-2']); + expect(objects.map((obj: SavedObject) => obj.id)).to.eql([ + 'only-ref-1', + 'ref-1-and-ref-2', + ]); }); }); @@ -315,7 +318,7 @@ export default function ({ getService }) { .expect(200) .then((resp) => { const objects = resp.body.saved_objects; - expect(objects.map((obj) => obj.id)).to.eql([ + expect(objects.map((obj: SavedObject) => obj.id)).to.eql([ 'only-ref-1', 'ref-1-and-ref-2', 'only-ref-2', @@ -337,7 +340,7 @@ export default function ({ getService }) { .expect(200) .then((resp) => { const objects = resp.body.saved_objects; - expect(objects.map((obj) => obj.id)).to.eql(['ref-1-and-ref-2']); + expect(objects.map((obj: SavedObject) => obj.id)).to.eql(['ref-1-and-ref-2']); }); }); }); @@ -358,7 +361,9 @@ export default function ({ getService }) { .expect(200) .then((resp) => { const savedObjects = resp.body.saved_objects; - expect(savedObjects.map((so) => so.attributes.title)).to.eql(['my-visualization']); + expect( + savedObjects.map((so: SavedObject<{ title: string }>) => so.attributes.title) + ).to.eql(['my-visualization']); })); it('can search with the prefix search character just after a special one', async () => @@ -372,7 +377,9 @@ export default function ({ getService }) { .expect(200) .then((resp) => { const savedObjects = resp.body.saved_objects; - expect(savedObjects.map((so) => so.attributes.title)).to.eql(['my-visualization']); + expect( + savedObjects.map((so: SavedObject<{ title: string }>) => so.attributes.title) + ).to.eql(['my-visualization']); })); it('can search for objects with asterisk', async () => @@ -386,7 +393,9 @@ export default function ({ getService }) { .expect(200) .then((resp) => { const savedObjects = resp.body.saved_objects; - expect(savedObjects.map((so) => so.attributes.title)).to.eql(['some*visualization']); + expect( + savedObjects.map((so: SavedObject<{ title: string }>) => so.attributes.title) + ).to.eql(['some*visualization']); })); it('can still search tokens by prefix', async () => @@ -400,10 +409,9 @@ export default function ({ getService }) { .expect(200) .then((resp) => { const savedObjects = resp.body.saved_objects; - expect(savedObjects.map((so) => so.attributes.title)).to.eql([ - 'my-visualization', - 'some*visualization', - ]); + expect( + savedObjects.map((so: SavedObject<{ title: string }>) => so.attributes.title) + ).to.eql(['my-visualization', 'some*visualization']); })); }); @@ -411,10 +419,7 @@ export default function ({ getService }) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return 200 with empty response', async () => @@ -513,7 +518,6 @@ export default function ({ getService }) { ) .expect(400) .then((resp) => { - console.log('body', JSON.stringify(resp.body)); expect(resp.body).to.eql({ error: 'Bad Request', message: 'This type dashboard is not allowed: Bad Request', diff --git a/test/api_integration/apis/saved_objects/get.js b/test/api_integration/apis/saved_objects/get.ts similarity index 93% rename from test/api_integration/apis/saved_objects/get.js rename to test/api_integration/apis/saved_objects/get.ts index f9c16c41c42f7..7134917122177 100644 --- a/test/api_integration/apis/saved_objects/get.js +++ b/test/api_integration/apis/saved_objects/get.ts @@ -7,10 +7,11 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const esArchiver = getService('esArchiver'); describe('get', () => { @@ -69,10 +70,7 @@ export default function ({ getService }) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return basic 404 without mentioning index', async () => diff --git a/test/api_integration/apis/saved_objects/index.js b/test/api_integration/apis/saved_objects/index.ts similarity index 88% rename from test/api_integration/apis/saved_objects/index.js rename to test/api_integration/apis/saved_objects/index.ts index 3feb8be0aa7f0..0e07b3c1ed060 100644 --- a/test/api_integration/apis/saved_objects/index.js +++ b/test/api_integration/apis/saved_objects/index.ts @@ -6,7 +6,9 @@ * Public License, v 1. */ -export default function ({ loadTestFile }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('saved_objects', () => { loadTestFile(require.resolve('./bulk_create')); loadTestFile(require.resolve('./bulk_get')); diff --git a/test/api_integration/apis/saved_objects/resolve_import_errors.js b/test/api_integration/apis/saved_objects/resolve_import_errors.ts similarity index 98% rename from test/api_integration/apis/saved_objects/resolve_import_errors.js rename to test/api_integration/apis/saved_objects/resolve_import_errors.ts index eafe350e76ae4..5f3929f26aba6 100644 --- a/test/api_integration/apis/saved_objects/resolve_import_errors.js +++ b/test/api_integration/apis/saved_objects/resolve_import_errors.ts @@ -8,8 +8,9 @@ import expect from '@kbn/expect'; import { join } from 'path'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); diff --git a/test/api_integration/apis/saved_objects/update.js b/test/api_integration/apis/saved_objects/update.ts similarity index 95% rename from test/api_integration/apis/saved_objects/update.js rename to test/api_integration/apis/saved_objects/update.ts index d7c80431e9860..d5346e82ce98c 100644 --- a/test/api_integration/apis/saved_objects/update.js +++ b/test/api_integration/apis/saved_objects/update.ts @@ -7,10 +7,11 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const esArchiver = getService('esArchiver'); describe('update', () => { @@ -120,10 +121,7 @@ export default function ({ getService }) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return generic 404', async () => diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index 66761e62bda52..d7b486e8ab5cf 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -11,7 +11,7 @@ import { Response } from 'supertest'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { - const es = getService('legacyEs'); + const es = getService('es'); const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); @@ -174,10 +174,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return 200 with empty response', async () => diff --git a/test/api_integration/apis/saved_objects_management/get.ts b/test/api_integration/apis/saved_objects_management/get.ts index 456711f06a1d7..bc05d7e392bb9 100644 --- a/test/api_integration/apis/saved_objects_management/get.ts +++ b/test/api_integration/apis/saved_objects_management/get.ts @@ -11,7 +11,7 @@ import { Response } from 'supertest'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { - const es = getService('legacyEs'); + const es = getService('es'); const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); @@ -45,10 +45,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return 404 for object that no longer exists', async () => diff --git a/test/api_integration/apis/telemetry/__fixtures__/ui_counters.js b/test/api_integration/apis/telemetry/__fixtures__/ui_counters.ts similarity index 100% rename from test/api_integration/apis/telemetry/__fixtures__/ui_counters.js rename to test/api_integration/apis/telemetry/__fixtures__/ui_counters.ts diff --git a/test/api_integration/apis/telemetry/telemetry_local.js b/test/api_integration/apis/telemetry/telemetry_local.ts similarity index 92% rename from test/api_integration/apis/telemetry/telemetry_local.js rename to test/api_integration/apis/telemetry/telemetry_local.ts index beba2e211b04c..25d29a807bdad 100644 --- a/test/api_integration/apis/telemetry/telemetry_local.js +++ b/test/api_integration/apis/telemetry/telemetry_local.ts @@ -9,22 +9,27 @@ import expect from '@kbn/expect'; import _ from 'lodash'; import { basicUiCounters } from './__fixtures__/ui_counters'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { SavedObject } from '../../../../src/core/server'; /* * Create a single-level array with strings for all the paths to values in the * source object, up to 3 deep. Going deeper than 3 causes a bit too much churn * in the tests. */ -function flatKeys(source) { - const recursivelyFlatKeys = (obj, path = [], depth = 0) => { +function flatKeys(source: Record) { + const recursivelyFlatKeys = (obj: unknown, path: string[] = [], depth = 0): string[] => { return depth < 3 && _.isObject(obj) - ? _.map(obj, (v, k) => recursivelyFlatKeys(v, [...path, k], depth + 1)) - : path.join('.'); + ? Object.entries(obj).reduce( + (acc, [k, v]) => [...acc, ...recursivelyFlatKeys(v, [...path, k], depth + 1)], + [] as string[] + ) + : [path.join('.')]; }; return _.uniq(_.flattenDeep(recursivelyFlatKeys(source))).sort((a, b) => a.localeCompare(b)); } -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('es'); const esArchiver = getService('esArchiver'); @@ -52,7 +57,7 @@ export default function ({ getService }) { const stats = body[0]; expect(stats.collection).to.be('local'); expect(stats.collectionSource).to.be('local'); - expect(stats.license).to.be.undefined; // OSS cannot get the license + expect(stats.license).to.be(undefined); // OSS cannot get the license expect(stats.stack_stats.kibana.count).to.be.a('number'); expect(stats.stack_stats.kibana.indices).to.be.a('number'); expect(stats.stack_stats.kibana.os.platforms[0].platform).to.be.a('string'); @@ -153,7 +158,7 @@ export default function ({ getService }) { }); describe('application usage limits', () => { - function createSavedObject(viewId) { + function createSavedObject(viewId?: string) { return supertest .post('/api/saved_objects/application_usage_transactional') .send({ @@ -170,7 +175,7 @@ export default function ({ getService }) { } describe('basic behaviour', () => { - let savedObjectIds = []; + let savedObjectIds: string[] = []; before('create application usage entries', async () => { savedObjectIds = await Promise.all([ createSavedObject(), @@ -245,7 +250,9 @@ export default function ({ getService }) { })) ) .expect(200) - .then((resp) => resp.body.saved_objects.forEach(({ id }) => savedObjectIds.push(id))); + .then((resp) => + resp.body.saved_objects.forEach(({ id }: SavedObject) => savedObjectIds.push(id)) + ); }); after('clean them all', async () => { // The SavedObjects API does not allow bulk deleting, and deleting one by one takes ages and the tests timeout diff --git a/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts b/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts index a3f24ab80389c..dd1aee560af86 100644 --- a/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts +++ b/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts @@ -7,37 +7,32 @@ */ import expect from '@kbn/expect'; -import { Client, DeleteDocumentParams, GetParams, GetResponse } from 'elasticsearch'; -import { TelemetrySavedObjectAttributes } from 'src/plugins/telemetry/server/telemetry_repository'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function optInTest({ getService }: FtrProviderContext) { - const client: Client = getService('legacyEs'); + const client = getService('es'); const supertest = getService('supertest'); describe('/api/telemetry/v2/userHasSeenNotice API Telemetry User has seen OptIn Notice', () => { it('should update telemetry setting field via PUT', async () => { - try { - await client.delete({ + await client.delete( + { index: '.kibana', id: 'telemetry:telemetry', - } as DeleteDocumentParams); - } catch (err) { - if (err.statusCode !== 404) { - throw err; - } - } + }, + { ignore: [404] } + ); await supertest.put('/api/telemetry/v2/userHasSeenNotice').set('kbn-xsrf', 'xxx').expect(200); const { - _source: { telemetry }, - }: GetResponse<{ - telemetry: TelemetrySavedObjectAttributes; - }> = await client.get({ + body: { + _source: { telemetry }, + }, + } = await client.get({ index: '.kibana', id: 'telemetry:telemetry', - } as GetParams); + }); expect(telemetry.userHasSeenNotice).to.be(true); }); diff --git a/test/api_integration/apis/ui_counters/index.js b/test/api_integration/apis/ui_counters/index.ts similarity index 77% rename from test/api_integration/apis/ui_counters/index.js rename to test/api_integration/apis/ui_counters/index.ts index f01855223264d..7944b38802d93 100644 --- a/test/api_integration/apis/ui_counters/index.js +++ b/test/api_integration/apis/ui_counters/index.ts @@ -6,7 +6,9 @@ * Public License, v 1. */ -export default function ({ loadTestFile }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('UI Counters', () => { loadTestFile(require.resolve('./ui_counters')); }); diff --git a/test/api_integration/apis/ui_counters/ui_counters.js b/test/api_integration/apis/ui_counters/ui_counters.ts similarity index 71% rename from test/api_integration/apis/ui_counters/ui_counters.js rename to test/api_integration/apis/ui_counters/ui_counters.ts index d84db0275349a..1cf16fe433bf9 100644 --- a/test/api_integration/apis/ui_counters/ui_counters.js +++ b/test/api_integration/apis/ui_counters/ui_counters.ts @@ -7,14 +7,15 @@ */ import expect from '@kbn/expect'; -import { ReportManager, METRIC_TYPE } from '@kbn/analytics'; +import { ReportManager, METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; import moment from 'moment'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); - const createUiCounterEvent = (eventName, type, count = 1) => ({ + const createUiCounterEvent = (eventName: string, type: UiCounterMetricType, count = 1) => ({ eventName, appName: 'myApp', type, @@ -38,9 +39,9 @@ export default function ({ getService }) { .send({ report }) .expect(200); - const response = await es.search({ index: '.kibana', q: 'type:ui-counter' }); + const { body: response } = await es.search({ index: '.kibana', q: 'type:ui-counter' }); - const ids = response.hits.hits.map(({ _id }) => _id); + const ids = response.hits.hits.map(({ _id }: { _id: string }) => _id); expect(ids.includes(`ui-counter:myApp:${dayDate}:${METRIC_TYPE.COUNT}:my_event`)).to.eql( true ); @@ -64,21 +65,26 @@ export default function ({ getService }) { .expect(200); const { - hits: { hits }, + body: { + hits: { hits }, + }, } = await es.search({ index: '.kibana', q: 'type:ui-counter' }); const countTypeEvent = hits.find( - (hit) => hit._id === `ui-counter:myApp:${dayDate}:${METRIC_TYPE.COUNT}:${uniqueEventName}` + (hit: { _id: string }) => + hit._id === `ui-counter:myApp:${dayDate}:${METRIC_TYPE.COUNT}:${uniqueEventName}` ); expect(countTypeEvent._source['ui-counter'].count).to.eql(1); const clickTypeEvent = hits.find( - (hit) => hit._id === `ui-counter:myApp:${dayDate}:${METRIC_TYPE.CLICK}:${uniqueEventName}` + (hit: { _id: string }) => + hit._id === `ui-counter:myApp:${dayDate}:${METRIC_TYPE.CLICK}:${uniqueEventName}` ); expect(clickTypeEvent._source['ui-counter'].count).to.eql(2); const secondEvent = hits.find( - (hit) => hit._id === `ui-counter:myApp:${dayDate}:${METRIC_TYPE.COUNT}:${uniqueEventName}_2` + (hit: { _id: string }) => + hit._id === `ui-counter:myApp:${dayDate}:${METRIC_TYPE.COUNT}:${uniqueEventName}_2` ); expect(secondEvent._source['ui-counter'].count).to.eql(1); }); diff --git a/test/api_integration/apis/ui_metric/index.js b/test/api_integration/apis/ui_metric/index.ts similarity index 76% rename from test/api_integration/apis/ui_metric/index.js rename to test/api_integration/apis/ui_metric/index.ts index 1cee9c395eb91..2b749f8a01196 100644 --- a/test/api_integration/apis/ui_metric/index.js +++ b/test/api_integration/apis/ui_metric/index.ts @@ -6,7 +6,9 @@ * Public License, v 1. */ -export default function ({ loadTestFile }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { describe('UI Metric', () => { loadTestFile(require.resolve('./ui_metric')); }); diff --git a/test/api_integration/apis/ui_metric/ui_metric.js b/test/api_integration/apis/ui_metric/ui_metric.ts similarity index 74% rename from test/api_integration/apis/ui_metric/ui_metric.js rename to test/api_integration/apis/ui_metric/ui_metric.ts index 0539bb25b1fd9..d330cb037d1a1 100644 --- a/test/api_integration/apis/ui_metric/ui_metric.js +++ b/test/api_integration/apis/ui_metric/ui_metric.ts @@ -7,20 +7,26 @@ */ import expect from '@kbn/expect'; -import { ReportManager, METRIC_TYPE } from '@kbn/analytics'; +import { ReportManager, METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; +import { UserAgentMetric } from '@kbn/analytics/target/types/metrics/user_agent'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); - const createStatsMetric = (eventName, type = METRIC_TYPE.CLICK, count = 1) => ({ + const createStatsMetric = ( + eventName: string, + type: UiCounterMetricType = METRIC_TYPE.CLICK, + count = 1 + ) => ({ eventName, appName: 'myApp', type, count, }); - const createUserAgentMetric = (appName) => ({ + const createUserAgentMetric = (appName: string): UserAgentMetric => ({ appName, type: METRIC_TYPE.USER_AGENT, userAgent: @@ -39,8 +45,8 @@ export default function ({ getService }) { .send({ report }) .expect(200); - const response = await es.search({ index: '.kibana', q: 'type:ui-metric' }); - const ids = response.hits.hits.map(({ _id }) => _id); + const { body: response } = await es.search({ index: '.kibana', q: 'type:ui-metric' }); + const ids = response.hits.hits.map(({ _id }: { _id: string }) => _id); expect(ids.includes('ui-metric:myApp:myEvent')).to.eql(true); }); @@ -64,8 +70,8 @@ export default function ({ getService }) { .send({ report }) .expect(200); - const response = await es.search({ index: '.kibana', q: 'type:ui-metric' }); - const ids = response.hits.hits.map(({ _id }) => _id); + const { body: response } = await es.search({ index: '.kibana', q: 'type:ui-metric' }); + const ids = response.hits.hits.map(({ _id }: { _id: string }) => _id); expect(ids.includes('ui-metric:myApp:myEvent')).to.eql(true); expect(ids.includes(`ui-metric:myApp:${uniqueEventName}`)).to.eql(true); expect(ids.includes(`ui-metric:kibana-user_agent:${userAgentMetric.userAgent}`)).to.eql(true); @@ -77,7 +83,6 @@ export default function ({ getService }) { const nano = hrTime[0] * 1000000000 + hrTime[1]; const uniqueEventName = `my_event_${nano}`; const { report } = reportManager.assignReports([ - , createStatsMetric(uniqueEventName, METRIC_TYPE.CLICK, 2), createStatsMetric(uniqueEventName, METRIC_TYPE.LOADED), ]); @@ -89,10 +94,14 @@ export default function ({ getService }) { .expect(200); const { - hits: { hits }, + body: { + hits: { hits }, + }, } = await es.search({ index: '.kibana', q: 'type:ui-metric' }); - const countTypeEvent = hits.find((hit) => hit._id === `ui-metric:myApp:${uniqueEventName}`); + const countTypeEvent = hits.find( + (hit: { _id: string }) => hit._id === `ui-metric:myApp:${uniqueEventName}` + ); expect(countTypeEvent._source['ui-metric'].count).to.eql(3); }); }); diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts b/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts index 4821eea095d88..6c1a9f0be2461 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts @@ -5,37 +5,32 @@ */ import expect from '@kbn/expect'; -import { Client, DeleteDocumentParams, GetParams, GetResponse } from 'elasticsearch'; -import { TelemetrySavedObjectAttributes } from '../../../../../src/plugins/telemetry/server/telemetry_repository'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function optInTest({ getService }: FtrProviderContext) { - const client: Client = getService('legacyEs'); + const client = getService('es'); const supertest = getService('supertest'); describe('/api/telemetry/v2/userHasSeenNotice API Telemetry User has seen OptIn Notice', () => { it('should update telemetry setting field via PUT', async () => { - try { - await client.delete({ + await client.delete( + { index: '.kibana', id: 'telemetry:telemetry', - } as DeleteDocumentParams); - } catch (err) { - if (err.statusCode !== 404) { - throw err; - } - } + }, + { ignore: [404] } + ); await supertest.put('/api/telemetry/v2/userHasSeenNotice').set('kbn-xsrf', 'xxx').expect(200); const { - _source: { telemetry }, - }: GetResponse<{ - telemetry: TelemetrySavedObjectAttributes; - }> = await client.get({ + body: { + _source: { telemetry }, + }, + } = await client.get({ index: '.kibana', id: 'telemetry:telemetry', - } as GetParams); + }); expect(telemetry.userHasSeenNotice).to.be(true); }); From c0438ad25ca4f30455a7174eee6ca3201d219adc Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Wed, 20 Jan 2021 16:40:13 +0100 Subject: [PATCH 06/83] [DX] migrate core xpack plugins to tsproject ref (#88676) * migrate cloud, beatsManagement and savedObjectTagging to TS project ref * migrates globalSearchBar * fix beatsManagement config * fix beatsManagement ts * fix beatsManagement config again * indent. --- .../components/navigation/child_routes.tsx | 2 +- .../beats_management/public/pages/index.ts | 3 +- x-pack/plugins/beats_management/tsconfig.json | 26 ++ x-pack/plugins/cloud/tsconfig.json | 21 ++ .../plugins/global_search_bar/tsconfig.json | 18 ++ .../saved_objects_tagging/tsconfig.json | 25 ++ x-pack/test/tsconfig.json | 8 +- x-pack/tsconfig.json | 225 +++++------------- x-pack/tsconfig.refs.json | 6 +- 9 files changed, 166 insertions(+), 168 deletions(-) create mode 100644 x-pack/plugins/beats_management/tsconfig.json create mode 100644 x-pack/plugins/cloud/tsconfig.json create mode 100644 x-pack/plugins/global_search_bar/tsconfig.json create mode 100644 x-pack/plugins/saved_objects_tagging/tsconfig.json diff --git a/x-pack/plugins/beats_management/public/components/navigation/child_routes.tsx b/x-pack/plugins/beats_management/public/components/navigation/child_routes.tsx index a7e705b00ee84..dd8388f7b7791 100644 --- a/x-pack/plugins/beats_management/public/components/navigation/child_routes.tsx +++ b/x-pack/plugins/beats_management/public/components/navigation/child_routes.tsx @@ -6,7 +6,7 @@ import React, { FC } from 'react'; import { Route, Switch } from 'react-router-dom'; -interface RouteConfig { +export interface RouteConfig { path: string; component: React.ComponentType; routes?: RouteConfig[]; diff --git a/x-pack/plugins/beats_management/public/pages/index.ts b/x-pack/plugins/beats_management/public/pages/index.ts index 418bc4d0ce147..e4b2788cf96e7 100644 --- a/x-pack/plugins/beats_management/public/pages/index.ts +++ b/x-pack/plugins/beats_management/public/pages/index.ts @@ -19,8 +19,9 @@ import { BeatsInitialEnrollmentPage } from './walkthrough/initial/beat'; import { FinishWalkthroughPage } from './walkthrough/initial/finish'; import { InitialWalkthroughPage } from './walkthrough/initial/index'; import { InitialTagPage } from './walkthrough/initial/tag'; +import type { RouteConfig } from '../components/navigation/child_routes'; -export const routeMap = [ +export const routeMap: RouteConfig[] = [ { path: '/tag/create/:tagid?', component: TagCreatePage }, { path: '/tag/edit/:tagid?', component: TagEditPage }, { diff --git a/x-pack/plugins/beats_management/tsconfig.json b/x-pack/plugins/beats_management/tsconfig.json new file mode 100644 index 0000000000000..ad68cc900e638 --- /dev/null +++ b/x-pack/plugins/beats_management/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "types/**/*", + "scripts/**/*", + "server/index_templates/*.json", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/data/tsconfig.json" }, + { "path": "../../../src/plugins/management/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../security/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/cloud/tsconfig.json b/x-pack/plugins/cloud/tsconfig.json new file mode 100644 index 0000000000000..46e81aa7fa086 --- /dev/null +++ b/x-pack/plugins/cloud/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../../../src/plugins/home/tsconfig.json" }, + { "path": "../security/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/global_search_bar/tsconfig.json b/x-pack/plugins/global_search_bar/tsconfig.json new file mode 100644 index 0000000000000..266eecc35c84b --- /dev/null +++ b/x-pack/plugins/global_search_bar/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["common/**/*", "public/**/*", "server/**/*"], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../global_search/tsconfig.json" }, + { "path": "../saved_objects_tagging/tsconfig.json" }, + { "path": "../security/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/saved_objects_tagging/tsconfig.json b/x-pack/plugins/saved_objects_tagging/tsconfig.json new file mode 100644 index 0000000000000..d00156ad1277c --- /dev/null +++ b/x-pack/plugins/saved_objects_tagging/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../../../src/plugins/management/tsconfig.json" }, + { "path": "../../../src/plugins/saved_objects_tagging_oss/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../security/tsconfig.json" }, + ] +} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index d977730181e89..cfe328236cd36 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -42,13 +42,17 @@ { "path": "../plugins/global_search_providers/tsconfig.json" }, { "path": "../plugins/features/tsconfig.json" }, { "path": "../plugins/embeddable_enhanced/tsconfig.json" }, - { "path": "../plugins/event_log/tsconfig.json"}, + { "path": "../plugins/event_log/tsconfig.json" }, { "path": "../plugins/licensing/tsconfig.json" }, { "path": "../plugins/task_manager/tsconfig.json" }, { "path": "../plugins/telemetry_collection_xpack/tsconfig.json" }, { "path": "../plugins/ui_actions_enhanced/tsconfig.json" }, { "path": "../plugins/spaces/tsconfig.json" }, { "path": "../plugins/security/tsconfig.json" }, - { "path": "../plugins/encrypted_saved_objects/tsconfig.json" } + { "path": "../plugins/encrypted_saved_objects/tsconfig.json" }, + { "path": "../plugins/beats_management/tsconfig.json" }, + { "path": "../plugins/cloud/tsconfig.json" }, + { "path": "../plugins/saved_objects_tagging/tsconfig.json" }, + { "path": "../plugins/global_search_bar/tsconfig.json" } ] } diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index c68d93903e705..812ead39ba412 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -1,11 +1,6 @@ { "extends": "../tsconfig.base.json", - "include": [ - "mocks.ts", - "typings/**/*", - "plugins/**/*", - "tasks/**/*" - ], + "include": ["mocks.ts", "typings/**/*", "plugins/**/*", "tasks/**/*"], "exclude": [ "plugins/apm/e2e/cypress/**/*", "plugins/apm/ftr_e2e/**/*", @@ -31,6 +26,10 @@ "plugins/spaces/**/*", "plugins/security/**/*", "plugins/encrypted_saved_objects/**/*", + "plugins/beats_management/**/*", + "plugins/cloud/**/*", + "plugins/saved_objects_tagging/**/*", + "plugins/global_search_bar/**/*", "test/**/*" ], "compilerOptions": { @@ -38,161 +37,61 @@ "incremental": false }, "references": [ - { - "path": "../src/core/tsconfig.json" - }, - { - "path": "../src/plugins/telemetry_management_section/tsconfig.json" - }, - { - "path": "../src/plugins/management/tsconfig.json" - }, - { - "path": "../src/plugins/bfetch/tsconfig.json" - }, - { - "path": "../src/plugins/charts/tsconfig.json" - }, - { - "path": "../src/plugins/console/tsconfig.json" - }, - { - "path": "../src/plugins/dashboard/tsconfig.json" - }, - { - "path": "../src/plugins/discover/tsconfig.json" - }, - { - "path": "../src/plugins/data/tsconfig.json" - }, - { - "path": "../src/plugins/dev_tools/tsconfig.json" - }, - { - "path": "../src/plugins/embeddable/tsconfig.json" - }, - { - "path": "../src/plugins/es_ui_shared/tsconfig.json" - }, - { - "path": "../src/plugins/expressions/tsconfig.json" - }, - { - "path": "../src/plugins/home/tsconfig.json" - }, - { - "path": "../src/plugins/inspector/tsconfig.json" - }, - { - "path": "../src/plugins/kibana_legacy/tsconfig.json" - }, - { - "path": "../src/plugins/kibana_react/tsconfig.json" - }, - { - "path": "../src/plugins/kibana_usage_collection/tsconfig.json" - }, - { - "path": "../src/plugins/kibana_utils/tsconfig.json" - }, - { - "path": "../src/plugins/navigation/tsconfig.json" - }, - { - "path": "../src/plugins/newsfeed/tsconfig.json" - }, - { - "path": "../src/plugins/saved_objects/tsconfig.json" - }, - { - "path": "../src/plugins/saved_objects_management/tsconfig.json" - }, - { - "path": "../src/plugins/saved_objects_tagging_oss/tsconfig.json" - }, - { - "path": "../src/plugins/presentation_util/tsconfig.json" - }, - { - "path": "../src/plugins/security_oss/tsconfig.json" - }, - { - "path": "../src/plugins/share/tsconfig.json" - }, - { - "path": "../src/plugins/telemetry/tsconfig.json" - }, - { - "path": "../src/plugins/telemetry_collection_manager/tsconfig.json" - }, - { - "path": "../src/plugins/url_forwarding/tsconfig.json" - }, - { - "path": "../src/plugins/ui_actions/tsconfig.json" - }, - { - "path": "../src/plugins/url_forwarding/tsconfig.json" - }, - { - "path": "../src/plugins/usage_collection/tsconfig.json" - }, - { - "path": "./plugins/console_extensions/tsconfig.json" - }, - { - "path": "./plugins/data_enhanced/tsconfig.json" - }, - { - "path": "./plugins/discover_enhanced/tsconfig.json" - }, - { - "path": "./plugins/global_search/tsconfig.json" - }, - { - "path": "./plugins/global_search_providers/tsconfig.json" - }, - { - "path": "./plugins/features/tsconfig.json" - }, - { - "path": "./plugins/graph/tsconfig.json" - }, - { - "path": "./plugins/embeddable_enhanced/tsconfig.json" - }, - { - "path": "./plugins/event_log/tsconfig.json" - }, - { - "path": "./plugins/licensing/tsconfig.json" - }, - { - "path": "./plugins/searchprofiler/tsconfig.json" - }, - { - "path": "./plugins/task_manager/tsconfig.json" - }, - { - "path": "./plugins/telemetry_collection_xpack/tsconfig.json" - }, - { - "path": "./plugins/ui_actions_enhanced/tsconfig.json" - }, - { - "path": "./plugins/vis_type_timeseries_enhanced/tsconfig.json" - }, - { - "path": "./plugins/translations/tsconfig.json" - }, - { - "path": "./plugins/spaces/tsconfig.json" - }, - { - "path": "./plugins/security/tsconfig.json" - }, - { - "path": "./plugins/encrypted_saved_objects/tsconfig.json" - } + { "path": "../src/core/tsconfig.json" }, + { "path": "../src/plugins/telemetry_management_section/tsconfig.json" }, + { "path": "../src/plugins/management/tsconfig.json" }, + { "path": "../src/plugins/bfetch/tsconfig.json" }, + { "path": "../src/plugins/charts/tsconfig.json" }, + { "path": "../src/plugins/console/tsconfig.json" }, + { "path": "../src/plugins/dashboard/tsconfig.json" }, + { "path": "../src/plugins/discover/tsconfig.json" }, + { "path": "../src/plugins/data/tsconfig.json" }, + { "path": "../src/plugins/dev_tools/tsconfig.json" }, + { "path": "../src/plugins/embeddable/tsconfig.json" }, + { "path": "../src/plugins/es_ui_shared/tsconfig.json" }, + { "path": "../src/plugins/expressions/tsconfig.json" }, + { "path": "../src/plugins/home/tsconfig.json" }, + { "path": "../src/plugins/inspector/tsconfig.json" }, + { "path": "../src/plugins/kibana_legacy/tsconfig.json" }, + { "path": "../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../src/plugins/kibana_usage_collection/tsconfig.json" }, + { "path": "../src/plugins/kibana_utils/tsconfig.json" }, + { "path": "../src/plugins/navigation/tsconfig.json" }, + { "path": "../src/plugins/newsfeed/tsconfig.json" }, + { "path": "../src/plugins/saved_objects/tsconfig.json" }, + { "path": "../src/plugins/saved_objects_management/tsconfig.json" }, + { "path": "../src/plugins/saved_objects_tagging_oss/tsconfig.json" }, + { "path": "../src/plugins/presentation_util/tsconfig.json" }, + { "path": "../src/plugins/security_oss/tsconfig.json" }, + { "path": "../src/plugins/share/tsconfig.json" }, + { "path": "../src/plugins/telemetry/tsconfig.json" }, + { "path": "../src/plugins/telemetry_collection_manager/tsconfig.json" }, + { "path": "../src/plugins/url_forwarding/tsconfig.json" }, + { "path": "../src/plugins/ui_actions/tsconfig.json" }, + { "path": "../src/plugins/url_forwarding/tsconfig.json" }, + { "path": "../src/plugins/usage_collection/tsconfig.json" }, + { "path": "./plugins/console_extensions/tsconfig.json" }, + { "path": "./plugins/data_enhanced/tsconfig.json" }, + { "path": "./plugins/discover_enhanced/tsconfig.json" }, + { "path": "./plugins/global_search/tsconfig.json" }, + { "path": "./plugins/global_search_providers/tsconfig.json" }, + { "path": "./plugins/features/tsconfig.json" }, + { "path": "./plugins/graph/tsconfig.json" }, + { "path": "./plugins/embeddable_enhanced/tsconfig.json" }, + { "path": "./plugins/event_log/tsconfig.json" }, + { "path": "./plugins/licensing/tsconfig.json" }, + { "path": "./plugins/searchprofiler/tsconfig.json" }, + { "path": "./plugins/task_manager/tsconfig.json" }, + { "path": "./plugins/telemetry_collection_xpack/tsconfig.json" }, + { "path": "./plugins/ui_actions_enhanced/tsconfig.json" }, + { "path": "./plugins/vis_type_timeseries_enhanced/tsconfig.json" }, + { "path": "./plugins/translations/tsconfig.json" }, + { "path": "./plugins/spaces/tsconfig.json" }, + { "path": "./plugins/security/tsconfig.json" }, + { "path": "./plugins/encrypted_saved_objects/tsconfig.json" }, + { "path": "./plugins/beats_management/tsconfig.json" }, + { "path": "./plugins/cloud/tsconfig.json" }, + { "path": "./plugins/saved_objects_tagging/tsconfig.json" }, + { "path": "./plugins/global_search_bar/tsconfig.json" } ] -} \ No newline at end of file +} diff --git a/x-pack/tsconfig.refs.json b/x-pack/tsconfig.refs.json index a0a9eda0aaf23..edee8e228f769 100644 --- a/x-pack/tsconfig.refs.json +++ b/x-pack/tsconfig.refs.json @@ -20,6 +20,10 @@ { "path": "./plugins/translations/tsconfig.json" }, { "path": "./plugins/spaces/tsconfig.json" }, { "path": "./plugins/security/tsconfig.json" }, - { "path": "./plugins/encrypted_saved_objects/tsconfig.json" } + { "path": "./plugins/encrypted_saved_objects/tsconfig.json" }, + { "path": "./plugins/beats_management/tsconfig.json" }, + { "path": "./plugins/cloud/tsconfig.json" }, + { "path": "./plugins/saved_objects_tagging/tsconfig.json" }, + { "path": "./plugins/global_search_bar/tsconfig.json" } ] } From 7297cc3b1d8f2ac47e3430c5983d696abde009f1 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Wed, 20 Jan 2021 16:44:59 +0100 Subject: [PATCH 07/83] [Lens] Add a11y test suite (#88623) * :white_check_mark: Add a11y test suite for Lens + some fixes * :wrench: Removed unused ids * :bug: Fix component duplication via key prop * :white_mark_check: Add more a11y tests * :recycle: Restored back reorder id + fix multi layer duplicate ids * :bug: Fix #88806 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../lens/public/drag_drop/providers.tsx | 2 +- .../editor_frame/config_panel/layer_panel.tsx | 9 +- .../public/editor_frame_service/service.tsx | 15 +- x-pack/test/accessibility/apps/lens.ts | 138 ++++++++++++++++++ x-pack/test/accessibility/config.ts | 1 + 5 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 x-pack/test/accessibility/apps/lens.ts diff --git a/x-pack/plugins/lens/public/drag_drop/providers.tsx b/x-pack/plugins/lens/public/drag_drop/providers.tsx index 46247fd981134..5e0fc648454ad 100644 --- a/x-pack/plugins/lens/public/drag_drop/providers.tsx +++ b/x-pack/plugins/lens/public/drag_drop/providers.tsx @@ -171,7 +171,7 @@ export function ReorderProvider({
-

+

{state.keyboardReorderMessage}

diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 831dfce2efadd..999f75686b1cb 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -193,7 +193,7 @@ export function LayerPanel( - {groups.map((group, groupIndex) => { + {groups.map((group) => { const newId = generateId(); const isMissing = !isEmptyLayer && group.required && group.accessors.length === 0; @@ -207,7 +207,7 @@ export function LayerPanel( fullWidth label={

{group.groupLabel}
} labelType="legend" - key={groupIndex} + key={group.groupId} isInvalid={isMissing} error={ isMissing ? ( @@ -222,7 +222,10 @@ export function LayerPanel( } > <> - + {group.accessors.map((accessorConfig) => { const accessor = accessorConfig.columnId; const { dragging } = dragDropContext; 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 d4e9522f3bed1..249d678f56af9 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -125,6 +125,12 @@ export class EditorFrameService { collectAsyncDefinitions(this.visualizations), ]); + const unmount = () => { + if (domElement) { + unmountComponentAtNode(domElement); + } + }; + return { mount: async ( element, @@ -141,6 +147,9 @@ export class EditorFrameService { searchSessionId, } ) => { + if (domElement !== element) { + unmount(); + } domElement = element; const firstDatasourceId = Object.keys(resolvedDatasources)[0]; const firstVisualizationId = Object.keys(resolvedVisualizations)[0]; @@ -179,11 +188,7 @@ export class EditorFrameService { domElement ); }, - unmount() { - if (domElement) { - unmountComponentAtNode(domElement); - } - }, + unmount, }; }; diff --git a/x-pack/test/accessibility/apps/lens.ts b/x-pack/test/accessibility/apps/lens.ts new file mode 100644 index 0000000000000..bfd79f070d284 --- /dev/null +++ b/x-pack/test/accessibility/apps/lens.ts @@ -0,0 +1,138 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'visualize', 'header', 'home', 'settings', 'lens']); + const a11y = getService('a11y'); + const testSubjects = getService('testSubjects'); + const listingTable = getService('listingTable'); + + describe('Lens', () => { + const lensChartName = 'MyLensChart'; + before(async () => { + await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { + useActualUrl: true, + }); + await PageObjects.home.addSampleDataSet('flights'); + }); + + after(async () => { + await PageObjects.common.navigateToApp('visualize'); + await listingTable.searchForItemWithName(lensChartName); + await listingTable.checkListingSelectAllCheckbox(); + await listingTable.clickDeleteSelected(); + await PageObjects.common.clickConfirmOnModal(); + }); + + it('lens', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await a11y.testAppSnapshot(); + }); + + it('lens XY chart', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: 'timestamp', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'avg', + field: 'AvgTicketPrice', + }); + + await a11y.testAppSnapshot(); + }); + + it('lens pie chart', async () => { + await PageObjects.lens.switchToVisualization('pie'); + await a11y.testAppSnapshot(); + }); + + it('lens datatable', async () => { + await PageObjects.lens.switchToVisualization('lnsDatatable'); + await a11y.testAppSnapshot(); + }); + + it('lens metric chart', async () => { + await PageObjects.lens.switchToVisualization('lnsMetric'); + await a11y.testAppSnapshot(); + }); + + it('dimension configuration panel', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + + await PageObjects.lens.openDimensionEditor('lnsXY_xDimensionPanel > lns-empty-dimension'); + await a11y.testAppSnapshot(); + + await PageObjects.lens.closeDimensionEditor(); + await PageObjects.lens.openDimensionEditor('lnsXY_yDimensionPanel > lns-empty-dimension'); + await a11y.testAppSnapshot(); + + await PageObjects.lens.closeDimensionEditor(); + }); + + it('change chart type', async () => { + await PageObjects.lens.switchToVisualization('line'); + await a11y.testAppSnapshot(); + }); + + it('change chart type via suggestions', async () => { + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: 'timestamp', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'avg', + field: 'AvgTicketPrice', + }); + + await testSubjects.click('lnsSuggestion-barChart > lnsSuggestion'); + await a11y.testAppSnapshot(); + }); + + // Skip until https://github.com/elastic/kibana/issues/88661 gets closed + it.skip('lens XY chart with multiple layers', async () => { + await PageObjects.lens.createLayer(); + + await PageObjects.lens.switchToVisualization('area'); + await PageObjects.lens.configureDimension( + { + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'DestCityName', + }, + 1 + ); + + await PageObjects.lens.configureDimension( + { + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'median', + field: 'FlightTimeMin', + }, + 1 + ); + await a11y.testAppSnapshot(); + }); + + it('saves lens chart', async () => { + await PageObjects.lens.save(lensChartName); + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts index 070e2c3a61f2b..cf13a009c2821 100644 --- a/x-pack/test/accessibility/config.ts +++ b/x-pack/test/accessibility/config.ts @@ -28,6 +28,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/kibana_overview'), require.resolve('./apps/ingest_node_pipelines'), require.resolve('./apps/ml'), + require.resolve('./apps/lens'), ], pageObjects, From 5b315858a3f1c07557c31c37e7f3f0f53e34f971 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Wed, 20 Jan 2021 10:07:13 -0600 Subject: [PATCH 08/83] Instances latency distribution chart (#88546) * Instances latency distribution chart Create an instances row component that does the data fetching for the instances table and the distribution chart. Use the same data for both the chart and the table. Tooltips and selection are disabled on the chart. * import fix * rename ServiceOverviewInstancesRow to ServiceOverviewInstancesChartAndTable * Updates based on feedback * remove stuff * hasdata --- .../components/app/service_overview/index.tsx | 15 ++- ...ice_overview_instances_chart_and_table.tsx | 75 +++++++++++++ .../index.tsx | 74 +++++-------- .../index.tsx | 101 ++++++++++++++++++ 4 files changed, 212 insertions(+), 53 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index f7720589359c8..c6cc59876fe35 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -19,7 +19,7 @@ import { SearchBar } from '../../shared/search_bar'; import { UserExperienceCallout } from '../transaction_overview/user_experience_callout'; import { ServiceOverviewDependenciesTable } from './service_overview_dependencies_table'; import { ServiceOverviewErrorsTable } from './service_overview_errors_table'; -import { ServiceOverviewInstancesTable } from './service_overview_instances_table'; +import { ServiceOverviewInstancesChartAndTable } from './service_overview_instances_chart_and_table'; import { ServiceOverviewThroughputChart } from './service_overview_throughput_chart'; import { ServiceOverviewTransactionsTable } from './service_overview_transactions_table'; import { useShouldUseMobileLayout } from './use_should_use_mobile_layout'; @@ -131,9 +131,16 @@ export function ServiceOverview({ {!isRumAgent && ( - - - + + + )} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx new file mode 100644 index 0000000000000..f7c2891bb3e65 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexItem, EuiPanel } from '@elastic/eui'; +import React from 'react'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { callApmApi } from '../../../services/rest/createCallApmApi'; +import { InstancesLatencyDistributionChart } from '../../shared/charts/instances_latency_distribution_chart'; +import { ServiceOverviewInstancesTable } from './service_overview_instances_table'; + +interface ServiceOverviewInstancesChartAndTableProps { + chartHeight: number; + serviceName: string; +} + +export function ServiceOverviewInstancesChartAndTable({ + chartHeight, + serviceName, +}: ServiceOverviewInstancesChartAndTableProps) { + const { transactionType } = useApmServiceContext(); + + const { + urlParams: { start, end }, + uiFilters, + } = useUrlParams(); + + const { data = [], status } = useFetcher(() => { + if (!start || !end || !transactionType) { + return; + } + + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/service_overview_instances', + params: { + path: { + serviceName, + }, + query: { + start, + end, + transactionType, + uiFilters: JSON.stringify(uiFilters), + numBuckets: 20, + }, + }, + }); + }, [start, end, serviceName, transactionType, uiFilters]); + + return ( + <> + + + + + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx index 1d0e1e50c1489..8d84ad7878ec7 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx @@ -4,52 +4,51 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexItem } from '@elastic/eui'; -import { EuiInMemoryTable } from '@elastic/eui'; -import { EuiTitle } from '@elastic/eui'; -import { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiFlexGroup } from '@elastic/eui'; +import { + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { ValuesType } from 'utility-types'; import { isJavaAgentName } from '../../../../../common/agent_name'; import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n'; import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes'; -import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { asMillisecondDuration, asPercent, asTransactionRate, } from '../../../../../common/utils/formatters'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { - APIReturnType, - callApmApi, -} from '../../../../services/rest/createCallApmApi'; -import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; -import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; -import { SparkPlot } from '../../../shared/charts/spark_plot'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { px, unit } from '../../../../style/variables'; -import { ServiceOverviewTableContainer } from '../service_overview_table_container'; -import { ServiceNodeMetricOverviewLink } from '../../../shared/Links/apm/ServiceNodeMetricOverviewLink'; +import { SparkPlot } from '../../../shared/charts/spark_plot'; import { MetricOverviewLink } from '../../../shared/Links/apm/MetricOverviewLink'; +import { ServiceNodeMetricOverviewLink } from '../../../shared/Links/apm/ServiceNodeMetricOverviewLink'; +import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; +import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; +import { ServiceOverviewTableContainer } from '../service_overview_table_container'; type ServiceInstanceItem = ValuesType< APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances'> >; interface Props { + items?: ServiceInstanceItem[]; serviceName: string; + status: FETCH_STATUS; } -export function ServiceOverviewInstancesTable({ serviceName }: Props) { - const { agentName, transactionType } = useApmServiceContext(); - - const { - urlParams: { start, end }, - uiFilters, - } = useUrlParams(); +export function ServiceOverviewInstancesTable({ + items = [], + serviceName, + status, +}: Props) { + const { agentName } = useApmServiceContext(); const columns: Array> = [ { @@ -197,31 +196,8 @@ export function ServiceOverviewInstancesTable({ serviceName }: Props) { }, ]; - const { data = [], status } = useFetcher(() => { - if (!start || !end || !transactionType) { - return; - } - - return callApmApi({ - endpoint: - 'GET /api/apm/services/{serviceName}/service_overview_instances', - params: { - path: { - serviceName, - }, - query: { - start, - end, - transactionType, - uiFilters: JSON.stringify(uiFilters), - numBuckets: 20, - }, - }, - }); - }, [start, end, serviceName, transactionType, uiFilters]); - // need top-level sortable fields for the managed table - const items = data.map((item) => ({ + const tableItems = items.map((item) => ({ ...item, latencyValue: item.latency?.value ?? 0, throughputValue: item.throughput?.value ?? 0, @@ -250,7 +226,7 @@ export function ServiceOverviewInstancesTable({ serviceName }: Props) { > ; + status: FETCH_STATUS; +} + +export function InstancesLatencyDistributionChart({ + height, + items = [], + status, +}: InstancesLatencyDistributionChartProps) { + const hasData = items.length > 0; + + const theme = useTheme(); + const chartTheme = { + ...useChartTheme(), + bubbleSeriesStyle: { + point: { + strokeWidth: 0, + fill: theme.eui.euiColorVis1, + radius: 4, + }, + }, + }; + + const maxLatency = Math.max(...items.map((item) => item.latency?.value ?? 0)); + const latencyFormatter = getDurationFormatter(maxLatency); + + return ( + + +

+ {i18n.translate('xpack.apm.instancesLatencyDistributionChartTitle', { + defaultMessage: 'Instances latency distribution', + })} +

+
+ + + + item.throughput?.value} + xScaleType={ScaleType.Linear} + yAccessors={[(item) => item.latency?.value]} + yScaleType={ScaleType.Linear} + /> + + + + +
+ ); +} From 0cd8c90a7cc9e98744acf25acfd3acc174386b88 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 20 Jan 2021 17:11:37 +0100 Subject: [PATCH 09/83] [Lens] (Accessibility) Added button to execute drag and drop to workspace (#85960) --- .../editor_frame/data_panel_wrapper.tsx | 6 +- .../editor_frame/editor_frame.test.tsx | 9 +- .../editor_frame/editor_frame.tsx | 59 +++++- .../editor_frame/suggestion_helpers.test.ts | 133 +++++++++++- .../editor_frame/suggestion_helpers.ts | 34 ++++ .../workspace_panel/workspace_panel.test.tsx | 189 +++--------------- .../workspace_panel/workspace_panel.tsx | 41 +--- .../datapanel.test.tsx | 2 + .../indexpattern_datasource/datapanel.tsx | 8 + .../indexpattern_datasource/field_item.scss | 3 +- .../field_item.test.tsx | 2 + .../indexpattern_datasource/field_item.tsx | 169 +++++++++++++--- .../indexpattern_datasource/field_list.tsx | 9 + .../fields_accordion.test.tsx | 2 + .../fields_accordion.tsx | 8 +- x-pack/plugins/lens/public/types.ts | 4 +- 16 files changed, 447 insertions(+), 231 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx index d00357058bb57..69bdff0151f6c 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/data_panel_wrapper.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { EuiPopover, EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; import { NativeRenderer } from '../../native_renderer'; import { Action } from './state_management'; -import { DragContext } from '../../drag_drop'; +import { DragContext, Dragging } from '../../drag_drop'; import { StateSetter, FramePublicAPI, DatasourceDataPanelProps, Datasource } from '../../types'; import { Query, Filter } from '../../../../../../src/plugins/data/public'; @@ -26,6 +26,8 @@ interface DataPanelWrapperProps { query: Query; dateRange: FramePublicAPI['dateRange']; filters: Filter[]; + dropOntoWorkspace: (field: Dragging) => void; + hasSuggestionForField: (field: Dragging) => boolean; } export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { @@ -51,6 +53,8 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => { dateRange: props.dateRange, filters: props.filters, showNoDataPopover: props.showNoDataPopover, + dropOntoWorkspace: props.dropOntoWorkspace, + hasSuggestionForField: props.hasSuggestionForField, }; const [showDatasourceSwitcher, setDatasourceSwitcher] = useState(false); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index ef95314c55581..76394c2901aaa 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -632,16 +632,19 @@ describe('editor_frame', () => { ); }); + const setDatasourceState = (mockDatasource.renderDataPanel as jest.Mock).mock.calls[0][1] + .setState; + + mockDatasource.renderDataPanel.mockClear(); + const updatedState = { title: 'shazm', }; - const setDatasourceState = (mockDatasource.renderDataPanel as jest.Mock).mock.calls[0][1] - .setState; act(() => { setDatasourceState(updatedState); }); - expect(mockDatasource.renderDataPanel).toHaveBeenCalledTimes(2); + expect(mockDatasource.renderDataPanel).toHaveBeenCalledTimes(1); expect(mockDatasource.renderDataPanel).toHaveBeenLastCalledWith( expect.any(Element), expect.objectContaining({ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index d872920d815ad..2cb815596d8b9 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -16,14 +16,19 @@ import { FrameLayout } from './frame_layout'; import { SuggestionPanel } from './suggestion_panel'; import { WorkspacePanel } from './workspace_panel'; import { Document } from '../../persistence/saved_object_store'; -import { RootDragDropProvider } from '../../drag_drop'; +import { Dragging, RootDragDropProvider } from '../../drag_drop'; import { getSavedObjectFormat } from './save'; import { generateId } from '../../id_generator'; import { Filter, Query, SavedQuery } from '../../../../../../src/plugins/data/public'; import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public'; import { EditorFrameStartPlugins } from '../service'; import { initializeDatasources, createDatasourceLayers } from './state_helpers'; -import { applyVisualizeFieldSuggestions } from './suggestion_helpers'; +import { + applyVisualizeFieldSuggestions, + getTopSuggestionForField, + switchToSuggestion, +} from './suggestion_helpers'; +import { trackUiEvent } from '../../lens_ui_telemetry'; export interface EditorFrameProps { doc?: Document; @@ -254,6 +259,53 @@ export function EditorFrame(props: EditorFrameProps) { ] ); + const getSuggestionForField = React.useCallback( + (field: Dragging) => { + const { activeDatasourceId, datasourceStates } = state; + const activeVisualizationId = state.visualization.activeId; + const visualizationState = state.visualization.state; + const { visualizationMap, datasourceMap } = props; + + if (!field || !activeDatasourceId) { + return; + } + + return getTopSuggestionForField( + datasourceLayers, + activeVisualizationId, + visualizationMap, + visualizationState, + datasourceMap[activeDatasourceId], + datasourceStates, + field + ); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + state.visualization.state, + props.datasourceMap, + props.visualizationMap, + state.activeDatasourceId, + state.datasourceStates, + ] + ); + + const hasSuggestionForField = React.useCallback( + (field: Dragging) => getSuggestionForField(field) !== undefined, + [getSuggestionForField] + ); + + const dropOntoWorkspace = React.useCallback( + (field) => { + const suggestion = getSuggestionForField(field); + if (suggestion) { + trackUiEvent('drop_onto_workspace'); + switchToSuggestion(dispatch, suggestion, 'SWITCH_VISUALIZATION'); + } + }, + [getSuggestionForField] + ); + return ( } configPanel={ @@ -310,6 +364,7 @@ export function EditorFrame(props: EditorFrameProps) { core={props.core} plugins={props.plugins} visualizeTriggerFieldContext={visualizeTriggerFieldContext} + getSuggestionForField={getSuggestionForField} /> ) } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts index c2534c8337df0..745a2250a1deb 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getSuggestions } from './suggestion_helpers'; +import { getSuggestions, getTopSuggestionForField } from './suggestion_helpers'; import { createMockVisualization, createMockDatasource, DatasourceMock } from '../mocks'; -import { TableSuggestion, DatasourceSuggestion } from '../../types'; +import { TableSuggestion, DatasourceSuggestion, Visualization } from '../../types'; import { PaletteOutput } from 'src/plugins/charts/public'; const generateSuggestion = (state = {}, layerId: string = 'first'): DatasourceSuggestion => ({ @@ -472,4 +472,133 @@ describe('suggestion helpers', () => { }) ); }); + + describe('getTopSuggestionForField', () => { + let mockVisualization1: jest.Mocked; + let mockVisualization2: jest.Mocked; + let mockDatasourceState: unknown; + let defaultParams: Parameters; + beforeEach(() => { + datasourceMap.mock.getDatasourceSuggestionsForField.mockReturnValue([ + { + state: {}, + table: { + isMultiRow: true, + layerId: '1', + columns: [], + changeType: 'unchanged', + }, + keptLayerIds: [], + }, + ]); + mockVisualization1 = createMockVisualization(); + mockVisualization1.getSuggestions.mockReturnValue([ + { + score: 0.3, + title: 'second suggestion', + state: { second: true }, + previewIcon: 'empty', + }, + { + score: 0.5, + title: 'top suggestion', + state: { first: true }, + previewIcon: 'empty', + }, + ]); + mockVisualization2 = createMockVisualization(); + mockVisualization2.getSuggestions.mockReturnValue([ + { + score: 0.8, + title: 'other vis suggestion', + state: {}, + previewIcon: 'empty', + }, + ]); + mockDatasourceState = { myDatasourceState: true }; + defaultParams = [ + { + '1': { + getTableSpec: () => [{ columnId: 'col1' }], + datasourceId: '', + getOperationForColumnId: jest.fn(), + }, + }, + 'vis1', + { vis1: mockVisualization1 }, + {}, + datasourceMap.mock, + { + mockindexpattern: { state: mockDatasourceState, isLoading: false }, + }, + { id: 'myfield' }, + ]; + }); + + it('should return top suggestion for field', () => { + const result = getTopSuggestionForField(...defaultParams); + expect(result!.title).toEqual('top suggestion'); + expect(datasourceMap.mock.getDatasourceSuggestionsForField).toHaveBeenCalledWith( + mockDatasourceState, + { + id: 'myfield', + } + ); + }); + + it('should return nothing if visualization does not produce suggestions', () => { + mockVisualization1.getSuggestions.mockReturnValue([]); + const result = getTopSuggestionForField(...defaultParams); + expect(result).toEqual(undefined); + }); + + it('should return nothing if datasource does not produce suggestions', () => { + datasourceMap.mock.getDatasourceSuggestionsForField.mockReturnValue([]); + defaultParams[2] = { + vis1: { ...mockVisualization1, getSuggestions: () => [] }, + vis2: mockVisualization2, + }; + const result = getTopSuggestionForField(...defaultParams); + expect(result).toEqual(undefined); + }); + + it('should not consider suggestion from other visualization if there is data', () => { + defaultParams[2] = { + vis1: { ...mockVisualization1, getSuggestions: () => [] }, + vis2: mockVisualization2, + }; + const result = getTopSuggestionForField(...defaultParams); + expect(result).toBeUndefined(); + }); + + it('should consider top suggestion from other visualization if there is no data', () => { + const mockVisualization3 = createMockVisualization(); + defaultParams[0] = { + '1': { + getTableSpec: () => [], + datasourceId: '', + getOperationForColumnId: jest.fn(), + }, + }; + mockVisualization1.getSuggestions.mockReturnValue([]); + mockVisualization3.getSuggestions.mockReturnValue([ + { + score: 0.1, + title: 'low ranking suggestion', + state: {}, + previewIcon: 'empty', + }, + ]); + defaultParams[2] = { + vis1: mockVisualization1, + vis2: mockVisualization2, + vis3: mockVisualization3, + }; + const result = getTopSuggestionForField(...defaultParams); + expect(result!.title).toEqual('other vis suggestion'); + expect(mockVisualization1.getSuggestions).toHaveBeenCalled(); + expect(mockVisualization2.getSuggestions).toHaveBeenCalled(); + expect(mockVisualization3.getSuggestions).toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts index daaf893f2a703..5cdc5ce592497 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts @@ -16,8 +16,10 @@ import { TableChangeType, TableSuggestion, DatasourceSuggestion, + DatasourcePublicAPI, } from '../../types'; import { Action } from './state_management'; +import { Dragging } from '../../drag_drop'; export interface Suggestion { visualizationId: string; @@ -221,3 +223,35 @@ export function switchToSuggestion( dispatch(action); } + +export function getTopSuggestionForField( + datasourceLayers: Record, + activeVisualizationId: string | null, + visualizationMap: Record>, + visualizationState: unknown, + datasource: Datasource, + datasourceStates: Record, + field: Dragging +) { + const hasData = Object.values(datasourceLayers).some( + (datasourceLayer) => datasourceLayer.getTableSpec().length > 0 + ); + + const mainPalette = + activeVisualizationId && visualizationMap[activeVisualizationId]?.getMainPalette + ? visualizationMap[activeVisualizationId].getMainPalette?.(visualizationState) + : undefined; + const suggestions = getSuggestions({ + datasourceMap: { [datasource.id]: datasource }, + datasourceStates, + visualizationMap: + hasData && activeVisualizationId + ? { [activeVisualizationId]: visualizationMap[activeVisualizationId] } + : visualizationMap, + activeVisualizationId, + visualizationState, + field, + mainPalette, + }); + return suggestions.find((s) => s.visualizationId === activeVisualizationId) || suggestions[0]; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index 6411b0e5f1ad9..ddb2640d50d59 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { ReactExpressionRendererProps } from '../../../../../../../src/plugins/expressions/public'; -import { FramePublicAPI, TableSuggestion, Visualization } from '../../../types'; +import { FramePublicAPI, Visualization } from '../../../types'; import { createMockVisualization, createMockDatasource, @@ -85,6 +85,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -108,6 +109,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -131,6 +133,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -168,6 +171,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -241,6 +245,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -284,6 +289,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -335,6 +341,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -415,6 +422,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); }); @@ -471,6 +479,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); }); @@ -528,6 +537,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -569,6 +579,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -612,6 +623,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -652,6 +664,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); @@ -690,6 +703,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); }); @@ -734,6 +748,7 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={() => undefined} /> ); }); @@ -756,6 +771,7 @@ describe('workspace_panel', () => { describe('suggestions from dropping in workspace panel', () => { let mockDispatch: jest.Mock; + let mockGetSuggestionForField: jest.Mock; let frame: jest.Mocked; const draggedField = { id: 'field' }; @@ -763,6 +779,7 @@ describe('workspace_panel', () => { beforeEach(() => { frame = createMockFramePublicAPI(); mockDispatch = jest.fn(); + mockGetSuggestionForField = jest.fn(); }); function initComponent(draggingContext = draggedField) { @@ -790,43 +807,23 @@ describe('workspace_panel', () => { ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} plugins={{ uiActions: uiActionsMock, data: dataMock }} + getSuggestionForField={mockGetSuggestionForField} /> ); } it('should immediately transition if exactly one suggestion is returned', () => { - const expectedTable: TableSuggestion = { - isMultiRow: true, - layerId: '1', - columns: [], - changeType: 'unchanged', - }; - mockDatasource.getDatasourceSuggestionsForField.mockReturnValueOnce([ - { - state: {}, - table: expectedTable, - keptLayerIds: [], - }, - ]); - mockVisualization.getSuggestions.mockReturnValueOnce([ - { - score: 0.5, - title: 'my title', - state: {}, - previewIcon: 'empty', - }, - ]); + mockGetSuggestionForField.mockReturnValue({ + visualizationId: 'vis', + datasourceState: {}, + datasourceId: 'mock', + visualizationState: {}, + }); initComponent(); instance.find(DragDrop).prop('onDrop')!(draggedField); - expect(mockDatasource.getDatasourceSuggestionsForField).toHaveBeenCalledTimes(1); - expect(mockVisualization.getSuggestions).toHaveBeenCalledWith( - expect.objectContaining({ - table: expectedTable, - }) - ); expect(mockDispatch).toHaveBeenCalledWith({ type: 'SWITCH_VISUALIZATION', newVisualizationId: 'vis', @@ -837,80 +834,12 @@ describe('workspace_panel', () => { }); it('should allow to drop if there are suggestions', () => { - mockDatasource.getDatasourceSuggestionsForField.mockReturnValueOnce([ - { - state: {}, - table: { - isMultiRow: true, - layerId: '1', - columns: [], - changeType: 'unchanged', - }, - keptLayerIds: [], - }, - ]); - mockVisualization.getSuggestions.mockReturnValueOnce([ - { - score: 0.5, - title: 'my title', - state: {}, - previewIcon: 'empty', - }, - ]); - initComponent(); - expect(instance.find(DragDrop).prop('droppable')).toBeTruthy(); - }); - - it('should refuse to drop if there only suggestions from other visualizations if there are data tables', () => { - frame.datasourceLayers.a = mockDatasource.publicAPIMock; - mockDatasource.publicAPIMock.getTableSpec.mockReturnValue([{ columnId: 'a' }]); - mockDatasource.getDatasourceSuggestionsForField.mockReturnValueOnce([ - { - state: {}, - table: { - isMultiRow: true, - layerId: '1', - columns: [], - changeType: 'unchanged', - }, - keptLayerIds: [], - }, - ]); - mockVisualization2.getSuggestions.mockReturnValueOnce([ - { - score: 0.5, - title: 'my title', - state: {}, - previewIcon: 'empty', - }, - ]); - initComponent(); - expect(instance.find(DragDrop).prop('droppable')).toBeFalsy(); - }); - - it('should allow to drop if there are suggestions from active visualization even if there are data tables', () => { - frame.datasourceLayers.a = mockDatasource.publicAPIMock; - mockDatasource.publicAPIMock.getTableSpec.mockReturnValue([{ columnId: 'a' }]); - mockDatasource.getDatasourceSuggestionsForField.mockReturnValueOnce([ - { - state: {}, - table: { - isMultiRow: true, - layerId: '1', - columns: [], - changeType: 'unchanged', - }, - keptLayerIds: [], - }, - ]); - mockVisualization.getSuggestions.mockReturnValueOnce([ - { - score: 0.5, - title: 'my title', - state: {}, - previewIcon: 'empty', - }, - ]); + mockGetSuggestionForField.mockReturnValue({ + visualizationId: 'vis', + datasourceState: {}, + datasourceId: 'mock', + visualizationState: {}, + }); initComponent(); expect(instance.find(DragDrop).prop('droppable')).toBeTruthy(); }); @@ -919,61 +848,5 @@ describe('workspace_panel', () => { initComponent(); expect(instance.find(DragDrop).prop('droppable')).toBeFalsy(); }); - - it('should immediately transition to the first suggestion if there are multiple', () => { - mockDatasource.getDatasourceSuggestionsForField.mockReturnValueOnce([ - { - state: {}, - table: { - isMultiRow: true, - columns: [], - layerId: '1', - changeType: 'unchanged', - }, - keptLayerIds: [], - }, - { - state: {}, - table: { - isMultiRow: true, - columns: [], - layerId: '1', - changeType: 'unchanged', - }, - keptLayerIds: [], - }, - ]); - mockVisualization.getSuggestions.mockReturnValueOnce([ - { - score: 0.5, - title: 'second suggestion', - state: {}, - previewIcon: 'empty', - }, - ]); - mockVisualization.getSuggestions.mockReturnValueOnce([ - { - score: 0.8, - title: 'first suggestion', - state: { - isFirst: true, - }, - previewIcon: 'empty', - }, - ]); - - initComponent(); - instance.find(DragDrop).prop('onDrop')!(draggedField); - - expect(mockDispatch).toHaveBeenCalledWith({ - type: 'SWITCH_VISUALIZATION', - newVisualizationId: 'vis', - initialState: { - isFirst: true, - }, - datasourceState: {}, - datasourceId: 'mock', - }); - }); }); }); 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 eb16dabfd2f90..5fc7b80a3d0ce 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 @@ -39,8 +39,8 @@ import { isLensFilterEvent, isLensEditEvent, } from '../../../types'; -import { DragDrop, DragContext } from '../../../drag_drop'; -import { getSuggestions, switchToSuggestion } from '../suggestion_helpers'; +import { DragDrop, DragContext, Dragging } from '../../../drag_drop'; +import { Suggestion, switchToSuggestion } from '../suggestion_helpers'; import { buildExpression } from '../expression_helpers'; import { debouncedComponent } from '../../../debounced_component'; import { trackUiEvent } from '../../../lens_ui_telemetry'; @@ -75,6 +75,7 @@ export interface WorkspacePanelProps { plugins: { uiActions?: UiActionsStart; data: DataPublicPluginStart }; title?: string; visualizeTriggerFieldContext?: VisualizeFieldContext; + getSuggestionForField: (field: Dragging) => Suggestion | undefined; } interface WorkspaceState { @@ -97,43 +98,11 @@ export function WorkspacePanel({ ExpressionRenderer: ExpressionRendererComponent, title, visualizeTriggerFieldContext, + getSuggestionForField, }: WorkspacePanelProps) { const dragDropContext = useContext(DragContext); - const suggestionForDraggedField = useMemo( - () => { - if (!dragDropContext.dragging || !activeDatasourceId) { - return; - } - - const hasData = Object.values(framePublicAPI.datasourceLayers).some( - (datasource) => datasource.getTableSpec().length > 0 - ); - - const mainPalette = - activeVisualizationId && - visualizationMap[activeVisualizationId] && - visualizationMap[activeVisualizationId].getMainPalette - ? visualizationMap[activeVisualizationId].getMainPalette!(visualizationState) - : undefined; - const suggestions = getSuggestions({ - datasourceMap: { [activeDatasourceId]: datasourceMap[activeDatasourceId] }, - datasourceStates, - visualizationMap: - hasData && activeVisualizationId - ? { [activeVisualizationId]: visualizationMap[activeVisualizationId] } - : visualizationMap, - activeVisualizationId, - visualizationState, - field: dragDropContext.dragging, - mainPalette, - }); - - return suggestions.find((s) => s.visualizationId === activeVisualizationId) || suggestions[0]; - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [dragDropContext.dragging] - ); + const suggestionForDraggedField = getSuggestionForField(dragDropContext.dragging); const [localState, setLocalState] = useState({ expressionBuildError: undefined, 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 3d55494fd260c..8e41abf23e934 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -260,6 +260,8 @@ describe('IndexPattern Data Panel', () => { query: { query: '', language: 'lucene' }, filters: [], showNoDataPopover: jest.fn(), + dropOntoWorkspace: jest.fn(), + hasSuggestionForField: jest.fn(() => false), }; }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 4bb18d1ee4a17..63f6f77dd10cc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -100,6 +100,8 @@ export function IndexPatternDataPanel({ changeIndexPattern, charts, showNoDataPopover, + dropOntoWorkspace, + hasSuggestionForField, }: Props) { const { indexPatternRefs, indexPatterns, currentIndexPatternId } = state; const onChangeIndexPattern = useCallback( @@ -193,6 +195,8 @@ export function IndexPatternDataPanel({ onChangeIndexPattern={onChangeIndexPattern} existingFields={state.existingFields} existenceFetchFailed={state.existenceFetchFailed} + dropOntoWorkspace={dropOntoWorkspace} + hasSuggestionForField={hasSuggestionForField} /> )} @@ -241,6 +245,8 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ data, existingFields, charts, + dropOntoWorkspace, + hasSuggestionForField, }: Omit & { data: DataPublicPluginStart; currentIndexPatternId: string; @@ -593,6 +599,8 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ currentIndexPatternId={currentIndexPatternId} existenceFetchFailed={existenceFetchFailed} existFieldsInIndex={!!allFields.length} + dropOntoWorkspace={dropOntoWorkspace} + hasSuggestionForField={hasSuggestionForField} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss index 1b55d9623e223..8c10ca9d30b73 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.scss @@ -42,7 +42,6 @@ max-width: 300px; } -.lnsFieldItem__buttonGroup { - // Enforce lowercase for buttons or else some browsers inherit all caps from flyout title +.lnsFieldItem__fieldPanelTitle { text-transform: none; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx index 2f3549911dfe7..1019b2c33e0e5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx @@ -95,6 +95,8 @@ describe('IndexPattern Field Item', () => { }, exists: true, chartsThemeService, + dropOntoWorkspace: () => {}, + hasSuggestionForField: () => false, }; data.fieldFormats = ({ 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 0e602893beae6..740b557b668b7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -6,10 +6,11 @@ import './field_item.scss'; -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import DateMath from '@elastic/datemath'; import { EuiButtonGroup, + EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiIconTip, @@ -18,7 +19,9 @@ import { EuiPopoverFooter, EuiPopoverTitle, EuiProgress, + EuiSpacer, EuiText, + EuiTitle, EuiToolTip, } from '@elastic/eui'; import { @@ -45,7 +48,7 @@ import { import { FieldButton } from '../../../../../src/plugins/kibana_react/public'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; import { DraggedField } from './indexpattern'; -import { DragDrop } from '../drag_drop'; +import { DragDrop, Dragging } from '../drag_drop'; import { DatasourceDataPanelProps, DataType } from '../types'; import { BucketedAggregation, FieldStatsResponse } from '../../common'; import { IndexPattern, IndexPatternField } from './types'; @@ -66,6 +69,8 @@ export interface FieldItemProps { chartsThemeService: ChartsPluginSetup['theme']; filters: Filter[]; hideDetails?: boolean; + dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; + hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; } interface State { @@ -95,10 +100,19 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { dateRange, filters, hideDetails, + dropOntoWorkspace, } = props; const [infoIsOpen, setOpen] = useState(false); + const dropOntoWorkspaceAndClose = useCallback( + (droppedField: Dragging) => { + dropOntoWorkspace(droppedField); + setOpen(false); + }, + [dropOntoWorkspace, setOpen] + ); + const [state, setState] = useState({ isLoading: false, }); @@ -142,10 +156,6 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { } function togglePopover() { - if (hideDetails) { - return; - } - setOpen(!infoIsOpen); if (!infoIsOpen) { trackUiEvent('indexpattern_field_info_click'); @@ -227,8 +237,13 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { closePopover={() => setOpen(false)} anchorPosition="rightUp" panelClassName="lnsFieldItem__fieldPanel" + initialFocus=".lnsFieldItem__fieldPanel" > - + ); @@ -236,6 +251,40 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { export const FieldItem = debouncedComponent(InnerFieldItem); +function FieldPanelHeader({ + indexPatternId, + field, + hasSuggestionForField, + dropOntoWorkspace, +}: { + field: IndexPatternField; + indexPatternId: string; + hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; + dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; +}) { + const draggableField = { + indexPatternId, + id: field.name, + field, + }; + + return ( + + + +
{field.displayName}
+
+
+ + +
+ ); +} + function FieldItemPopoverContents(props: State & FieldItemProps) { const { histogram, @@ -247,6 +296,9 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { sampledValues, chartsThemeService, data: { fieldFormats }, + dropOntoWorkspace, + hasSuggestionForField, + hideDetails, } = props; const chartTheme = chartsThemeService.useChartsTheme(); @@ -270,6 +322,19 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { const [showingHistogram, setShowingHistogram] = useState(histogramDefault); + const panelHeader = ( + + ); + + if (hideDetails) { + return panelHeader; + } + let formatter: { convert: (data: unknown) => string }; if (indexPattern.fieldFormatMap && indexPattern.fieldFormatMap[field.name]) { const FormatType = fieldFormats.getType(indexPattern.fieldFormatMap[field.name].id); @@ -300,12 +365,16 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { (!props.topValues || props.topValues.buckets.length === 0) ) { return ( - - {i18n.translate('xpack.lens.indexPattern.fieldStatsNoData', { - defaultMessage: - '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.', - })} - + <> + {panelHeader} + + + {i18n.translate('xpack.lens.indexPattern.fieldStatsNoData', { + defaultMessage: + '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.', + })} + + ); } @@ -340,31 +409,40 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { ); } else if (field.type === 'date') { title = ( - <> - {i18n.translate('xpack.lens.indexPattern.fieldTimeDistributionLabel', { - defaultMessage: 'Time distribution', - })} - + +
+ {i18n.translate('xpack.lens.indexPattern.fieldTimeDistributionLabel', { + defaultMessage: 'Time distribution', + })} +
+
); } else if (topValues && topValues.buckets.length) { title = ( - <> - {i18n.translate('xpack.lens.indexPattern.fieldTopValuesLabel', { - defaultMessage: 'Top values', - })} - + +
+ {i18n.translate('xpack.lens.indexPattern.fieldTopValuesLabel', { + defaultMessage: 'Top values', + })} +
+
); } function wrapInPopover(el: React.ReactElement) { return ( <> - {title ? {title} : <>} + {panelHeader} + + {title ? title : <>} + + + {el} {props.totalDocuments ? ( - + {props.sampledDocuments && ( <> {i18n.translate('xpack.lens.indexPattern.percentageOfLabel', { @@ -552,3 +630,44 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { } return <>; } + +const DragToWorkspaceButton = ({ + field, + dropOntoWorkspace, + isEnabled, +}: { + field: { + indexPatternId: string; + id: string; + field: IndexPatternField; + }; + dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; + isEnabled: boolean; +}) => { + const buttonTitle = isEnabled + ? i18n.translate('xpack.lens.indexPattern.moveToWorkspace', { + defaultMessage: 'Add {field} to workspace', + values: { + field: field.field.name, + }, + }) + : i18n.translate('xpack.lens.indexPattern.moveToWorkspaceDisabled', { + defaultMessage: + "This field can't be added to the workspace automatically. You can still use it directly in the configuration panel.", + }); + + return ( + + + { + dropOntoWorkspace(field); + }} + /> + + + ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx index 5668e85510f9d..7bcd44e3d25d0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx @@ -12,6 +12,7 @@ import { FieldItem } from './field_item'; import { NoFieldsCallout } from './no_fields_callout'; import { IndexPatternField } from './types'; import { FieldItemSharedProps, FieldsAccordion } from './fields_accordion'; +import { DatasourceDataPanelProps } from '../types'; const PAGINATION_SIZE = 50; export type FieldGroups = Record< @@ -48,6 +49,8 @@ export function FieldList({ filter, currentIndexPatternId, existFieldsInIndex, + dropOntoWorkspace, + hasSuggestionForField, }: { exists: (field: IndexPatternField) => boolean; fieldGroups: FieldGroups; @@ -60,6 +63,8 @@ export function FieldList({ }; currentIndexPatternId: string; existFieldsInIndex: boolean; + dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; + hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; }) { const [pageSize, setPageSize] = useState(PAGINATION_SIZE); const [scrollContainer, setScrollContainer] = useState(undefined); @@ -137,6 +142,8 @@ export function FieldList({ field={field} hideDetails={true} key={field.name} + dropOntoWorkspace={dropOntoWorkspace} + hasSuggestionForField={hasSuggestionForField} /> )) )} @@ -147,6 +154,8 @@ export function FieldList({ .map(([key, fieldGroup]) => ( { fieldProps, renderCallout:
Callout
, exists: () => true, + dropOntoWorkspace: () => {}, + hasSuggestionForField: () => false, }; }); 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 ef249f87f05e4..11adf1a128c1b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx @@ -50,6 +50,8 @@ export interface FieldsAccordionProps { exists: (field: IndexPatternField) => boolean; showExistenceFetchError?: boolean; hideDetails?: boolean; + dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; + hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; } export const InnerFieldsAccordion = function InnerFieldsAccordion({ @@ -67,6 +69,8 @@ export const InnerFieldsAccordion = function InnerFieldsAccordion({ exists, hideDetails, showExistenceFetchError, + dropOntoWorkspace, + hasSuggestionForField, }: FieldsAccordionProps) { const renderField = useCallback( (field: IndexPatternField) => ( @@ -76,9 +80,11 @@ export const InnerFieldsAccordion = function InnerFieldsAccordion({ field={field} exists={exists(field)} hideDetails={hideDetails} + dropOntoWorkspace={dropOntoWorkspace} + hasSuggestionForField={hasSuggestionForField} /> ), - [fieldProps, exists, hideDetails] + [fieldProps, exists, hideDetails, dropOntoWorkspace, hasSuggestionForField] ); const titleClassname = classNames({ diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index fe35e07081bf3..bba601f942380 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -16,7 +16,7 @@ import { Datatable, SerializedFieldFormat, } from '../../../../src/plugins/expressions/public'; -import { DragContextState } from './drag_drop'; +import { DragContextState, Dragging } from './drag_drop'; import { Document } from './persistence'; import { DateRange } from '../common'; import { Query, Filter, SavedQuery, IFieldFormat } from '../../../../src/plugins/data/public'; @@ -217,6 +217,8 @@ export interface DatasourceDataPanelProps { query: Query; dateRange: DateRange; filters: Filter[]; + dropOntoWorkspace: (field: Dragging) => void; + hasSuggestionForField: (field: Dragging) => boolean; } interface SharedDimensionProps { From 48cb37945a990fd062b61864f416ddb39aa02290 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 20 Jan 2021 17:24:31 +0100 Subject: [PATCH 10/83] [UX] fix impacted page load errors (#88597) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/queries.test.ts.snap | 14 + .../server/lib/rum_client/get_js_errors.ts | 20 +- .../es_archiver/rum_test_data/data.json.gz | Bin 0 -> 836153 bytes .../es_archiver/rum_test_data/mappings.json | 9145 +++++++++++++++++ .../trial/tests/csm/js_errors.ts | 40 +- 5 files changed, 9210 insertions(+), 9 deletions(-) create mode 100644 x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data/data.json.gz create mode 100644 x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data/mappings.json diff --git a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap index b89c46f6e3fc5..6b4bc844f21c3 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap @@ -89,6 +89,20 @@ Object { "size": 5, }, }, + "impactedPages": Object { + "aggs": Object { + "pageCount": Object { + "cardinality": Object { + "field": "transaction.id", + }, + }, + }, + "filter": Object { + "term": Object { + "transaction.type": "page-load", + }, + }, + }, "sample": Object { "top_hits": Object { "_source": Array [ diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts b/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts index bbceef418ea45..229a2ef63e481 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts @@ -12,7 +12,9 @@ import { ERROR_EXC_TYPE, ERROR_GROUP_ID, TRANSACTION_ID, + TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; +import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types'; export async function getJSErrors({ setup, @@ -57,6 +59,20 @@ export async function getJSErrors({ from: pageIndex * pageSize, }, }, + impactedPages: { + filter: { + term: { + [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD, + }, + }, + aggs: { + pageCount: { + cardinality: { + field: TRANSACTION_ID, + }, + }, + }, + }, sample: { top_hits: { _source: [ @@ -86,9 +102,9 @@ export async function getJSErrors({ totalErrorPages: totalErrorPages?.value ?? 0, totalErrors: response.hits.total.value ?? 0, totalErrorGroups: totalErrorGroups?.value ?? 0, - items: errors?.buckets.map(({ sample, doc_count: count, key }) => { + items: errors?.buckets.map(({ sample, key, impactedPages }) => { return { - count, + count: impactedPages.pageCount.value, errorGroupId: key, errorMessage: (sample.hits.hits[0]._source as { error: { exception: Array<{ message: string }> }; diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data/data.json.gz b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..92e8e04f63af6549ea2300f5c9ceb96f038281b8 GIT binary patch literal 836153 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�L9}u{K~xhO303_~7wSYi5>eGgETAC* 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

GQC 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

)4{=y|Lu(@s@Ek7$-hu=Y$+ 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

-af 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 literal 0 HcmV?d00001 diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data/mappings.json b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data/mappings.json new file mode 100644 index 0000000000000..3966692d5a4fc --- /dev/null +++ b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data/mappings.json @@ -0,0 +1,9145 @@ +{ + "type": "index", + "value": { + "index": "apm-8.0.0-error-2020.12.03-000005", + "mappings": { + "_meta": { + "beat": "apm", + "version": "8.0.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "transaction.marks": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "transaction.marks.*" + } + }, + { + "transaction.marks.*.*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "transaction.marks.*.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "dynamic": "false", + "properties": { + "build": { + "properties": { + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "path": "agent.name", + "type": "alias" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "child": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "dynamic": "false", + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "container": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "dynamic": "false", + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "culprit": { + "ignore_above": 1024, + "type": "keyword" + }, + "exception": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handled": { + "type": "boolean" + }, + "message": { + "norms": false, + "type": "text" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "grouping_key": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "param_message": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "experimental": { + "dynamic": "true", + "type": "object" + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "dynamic": "false", + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "dynamic": "false", + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "finished": { + "type": "boolean" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kubernetes": { + "dynamic": "false", + "properties": { + "annotations": { + "properties": { + "*": { + "type": "object" + } + } + }, + "container": { + "properties": { + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "dynamic": "true", + "properties": { + "city": { + "type": "keyword" + }, + "company": { + "type": "keyword" + }, + "country_code": { + "type": "keyword" + }, + "customer_tier": { + "type": "keyword" + }, + "foo": { + "type": "keyword" + }, + "git_rev": { + "type": "keyword" + }, + "in_eu": { + "type": "boolean" + }, + "ip": { + "type": "keyword" + }, + "lang": { + "type": "keyword" + }, + "lorem": { + "type": "keyword" + }, + "multi-line": { + "type": "keyword" + }, + "request_id": { + "type": "keyword" + }, + "this-is-a-very-long-tag-name-without-any-spaces": { + "type": "keyword" + }, + "u": { + "type": "keyword" + } + } + }, + "log": { + "properties": { + "file": { + "properties": { + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "metricset": { + "properties": { + "period": { + "type": "long" + } + } + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "inner": { + "properties": { + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "observer": { + "dynamic": "false", + "properties": { + "egress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "listening": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_major": { + "type": "byte" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "parent": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "dynamic": "false", + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "processor": { + "properties": { + "event": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "profile": { + "dynamic": "false", + "properties": { + "alloc_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "alloc_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "cpu": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "duration": { + "type": "long" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "inuse_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "inuse_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "samples": { + "properties": { + "count": { + "type": "long" + } + } + }, + "stack": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + }, + "top": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "author": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "dynamic": "false", + "properties": { + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "framework": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "language": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "sourcemap": { + "dynamic": "false", + "properties": { + "bundle_filepath": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "span": { + "dynamic": "false", + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "db": { + "dynamic": "false", + "properties": { + "link": { + "ignore_above": 1024, + "type": "keyword" + }, + "rows_affected": { + "type": "long" + } + } + }, + "destination": { + "dynamic": "false", + "properties": { + "service": { + "dynamic": "false", + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "ignore_above": 1024, + "type": "keyword" + }, + "response_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "start": { + "properties": { + "us": { + "type": "long" + } + } + }, + "subtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "sync": { + "type": "boolean" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "system": { + "properties": { + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "actual": { + "properties": { + "free": { + "type": "long" + } + } + }, + "total": { + "type": "long" + } + } + }, + "process": { + "properties": { + "cgroup": { + "properties": { + "memory": { + "properties": { + "mem": { + "properties": { + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "rss": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "size": { + "type": "long" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "properties": { + "us": { + "type": "long" + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tracing": { + "properties": { + "span": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "transaction": { + "dynamic": "false", + "properties": { + "breakdown": { + "properties": { + "count": { + "type": "long" + } + } + }, + "duration": { + "properties": { + "count": { + "type": "long" + }, + "histogram": { + "type": "histogram" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + }, + "us": { + "type": "long" + } + } + }, + "experience": { + "properties": { + "cls": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "fid": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "longtask": { + "properties": { + "count": { + "type": "long" + }, + "max": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "sum": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "tbt": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "marks": { + "dynamic": "true", + "properties": { + "*": { + "properties": { + "*": { + "dynamic": "true", + "type": "object" + } + } + } + } + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "root": { + "type": "boolean" + }, + "sampled": { + "type": "boolean" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "span_count": { + "properties": { + "dropped": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "dynamic": "false", + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "dynamic": "false", + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "dynamic": "false", + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "view spans": { + "ignore_above": 1024, + "type": "keyword" + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "apm-rollover-30-days", + "rollover_alias": "apm-8.0.0-error" + }, + "mapping": { + "total_fields": { + "limit": "2000" + } + }, + "max_docvalue_fields_search": "200", + "number_of_replicas": "1", + "number_of_shards": "1", + "priority": "100", + "refresh_interval": "5s" + } + } + } +} + +{ + "type": "index", + "value": { + "index": "apm-8.0.0-transaction-2020.12.03-000005", + "mappings": { + "_meta": { + "beat": "apm", + "version": "8.0.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "labels_string": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels_boolean": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels_*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "transaction.marks": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "transaction.marks.*" + } + }, + { + "transaction.marks.*.*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "transaction.marks.*.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "dynamic": "false", + "properties": { + "build": { + "properties": { + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "path": "agent.name", + "type": "alias" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "child": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "dynamic": "false", + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "container": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "dynamic": "false", + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "culprit": { + "ignore_above": 1024, + "type": "keyword" + }, + "exception": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handled": { + "type": "boolean" + }, + "message": { + "norms": false, + "type": "text" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "grouping_key": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "param_message": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "experimental": { + "dynamic": "true", + "type": "object" + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "dynamic": "false", + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "dynamic": "false", + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "finished": { + "type": "boolean" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kubernetes": { + "dynamic": "false", + "properties": { + "annotations": { + "properties": { + "*": { + "type": "object" + } + } + }, + "container": { + "properties": { + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "dynamic": "true", + "properties": { + "city": { + "type": "keyword" + }, + "company": { + "type": "keyword" + }, + "country_code": { + "type": "keyword" + }, + "customer_email": { + "type": "keyword" + }, + "customer_name": { + "type": "keyword" + }, + "customer_tier": { + "type": "keyword" + }, + "foo": { + "type": "keyword" + }, + "git_rev": { + "type": "keyword" + }, + "in_eu": { + "type": "boolean" + }, + "ip": { + "type": "keyword" + }, + "lang": { + "type": "keyword" + }, + "lorem": { + "type": "keyword" + }, + "multi-line": { + "type": "keyword" + }, + "request_id": { + "type": "keyword" + }, + "served_from_cache": { + "type": "keyword" + }, + "this-is-a-very-long-tag-name-without-any-spaces": { + "type": "keyword" + }, + "u": { + "type": "keyword" + }, + "worker": { + "type": "keyword" + } + } + }, + "log": { + "properties": { + "file": { + "properties": { + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "metricset": { + "properties": { + "period": { + "type": "long" + } + } + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "inner": { + "properties": { + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "observer": { + "dynamic": "false", + "properties": { + "egress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingress": { + "properties": { + "interface": { + "properties": { + "alias": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "listening": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_major": { + "type": "byte" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "parent": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "dynamic": "false", + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "imphash": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "processor": { + "properties": { + "event": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "profile": { + "dynamic": "false", + "properties": { + "alloc_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "alloc_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "cpu": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "duration": { + "type": "long" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "inuse_objects": { + "properties": { + "count": { + "type": "long" + } + } + }, + "inuse_space": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "samples": { + "properties": { + "count": { + "type": "long" + } + } + }, + "stack": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + }, + "top": { + "dynamic": "false", + "properties": { + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "line": { + "type": "long" + } + } + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "author": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "dynamic": "false", + "properties": { + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "framework": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "language": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "sourcemap": { + "dynamic": "false", + "properties": { + "bundle_filepath": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "span": { + "dynamic": "false", + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "db": { + "dynamic": "false", + "properties": { + "link": { + "ignore_above": 1024, + "type": "keyword" + }, + "rows_affected": { + "type": "long" + } + } + }, + "destination": { + "dynamic": "false", + "properties": { + "service": { + "dynamic": "false", + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "ignore_above": 1024, + "type": "keyword" + }, + "response_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "start": { + "properties": { + "us": { + "type": "long" + } + } + }, + "subtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "sync": { + "type": "boolean" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "system": { + "properties": { + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "actual": { + "properties": { + "free": { + "type": "long" + } + } + }, + "total": { + "type": "long" + } + } + }, + "process": { + "properties": { + "cgroup": { + "properties": { + "memory": { + "properties": { + "mem": { + "properties": { + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "rss": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "size": { + "type": "long" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "properties": { + "us": { + "type": "long" + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tracing": { + "properties": { + "span": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "transaction": { + "dynamic": "false", + "properties": { + "breakdown": { + "properties": { + "count": { + "type": "long" + } + } + }, + "duration": { + "properties": { + "count": { + "type": "long" + }, + "histogram": { + "type": "histogram" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + }, + "us": { + "type": "long" + } + } + }, + "experience": { + "properties": { + "cls": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "fid": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "longtask": { + "properties": { + "count": { + "type": "long" + }, + "max": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "sum": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "tbt": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "marks": { + "dynamic": "true", + "properties": { + "*": { + "properties": { + "*": { + "dynamic": "true", + "type": "object" + } + } + }, + "agent": { + "properties": { + "domComplete": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domInteractive": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "firstContentfulPaint": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "largestContentfulPaint": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "timeToFirstByte": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "navigationTiming": { + "properties": { + "connectEnd": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "connectStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domComplete": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domContentLoadedEventEnd": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domContentLoadedEventStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domInteractive": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domLoading": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domainLookupEnd": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "domainLookupStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "fetchStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "loadEventEnd": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "loadEventStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "requestStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "responseEnd": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "responseStart": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + } + } + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "root": { + "type": "boolean" + }, + "sampled": { + "type": "boolean" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "span_count": { + "properties": { + "dropped": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "dynamic": "false", + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "dynamic": "false", + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "dynamic": "false", + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "view spans": { + "ignore_above": 1024, + "type": "keyword" + }, + "vlan": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_exponent": { + "index": false, + "type": "long" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "distinguished_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_or_province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version_number": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "apm-rollover-30-days", + "rollover_alias": "apm-8.0.0-transaction" + }, + "mapping": { + "total_fields": { + "limit": "2000" + } + }, + "max_docvalue_fields_search": "200", + "number_of_replicas": "1", + "number_of_shards": "1", + "priority": "100", + "refresh_interval": "5s" + } + } + } +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts b/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts index 6fc8cb4c1d4e1..6abb701f98a1c 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts @@ -32,26 +32,52 @@ export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) describe('when there is data', () => { before(async () => { await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); + await esArchiver.load('rum_test_data'); }); after(async () => { await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); + await esArchiver.unload('rum_test_data'); }); it('returns js errors', async () => { const response = await supertest.get( - '/api/apm/rum-client/js-errors?pageSize=5&pageIndex=0&start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' + '/api/apm/rum-client/js-errors?start=2021-01-18T12%3A20%3A17.202Z&end=2021-01-18T12%3A25%3A17.203Z&uiFilters=%7B%22environment%22%3A%22ENVIRONMENT_ALL%22%2C%22serviceName%22%3A%5B%22elastic-co-frontend%22%5D%7D&pageSize=5&pageIndex=0' ); expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` Object { - "items": Array [], - "totalErrorGroups": 0, - "totalErrorPages": 0, - "totalErrors": 0, + "items": Array [ + Object { + "count": 5, + "errorGroupId": "de32dc81e2ee5165cbff20046c080a27", + "errorMessage": "SyntaxError: Document.querySelector: '' is not a valid selector", + }, + Object { + "count": 2, + "errorGroupId": "34d83587e17711a7c257ffb080ddb1c6", + "errorMessage": "Uncaught SyntaxError: Failed to execute 'querySelector' on 'Document': The provided selector is empty.", + }, + Object { + "count": 43, + "errorGroupId": "3dd5604267b928139d958706f09f7e09", + "errorMessage": "Script error.", + }, + Object { + "count": 1, + "errorGroupId": "cd3a2b01017ff7bcce70479644f28318", + "errorMessage": "Unhandled promise rejection: TypeError: can't convert undefined to object", + }, + Object { + "count": 3, + "errorGroupId": "23539422cf714db071aba087dd041859", + "errorMessage": "Unable to get property 'left' of undefined or null reference", + }, + ], + "totalErrorGroups": 6, + "totalErrorPages": 120, + "totalErrors": 2846, } `); }); From c702b5cfe84ae7159669e39ce67b00e04bd528fa Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Wed, 20 Jan 2021 17:30:39 +0100 Subject: [PATCH 11/83] Skip snapshot_restore API integration tests in cloud (#88841) This PR disables the snapshot restore API integration tests in cloud. --- .../apis/management/snapshot_restore/snapshot_restore.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/snapshot_restore.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/snapshot_restore.ts index 575da0db2a759..1778e4f1210e5 100644 --- a/x-pack/test/api_integration/apis/management/snapshot_restore/snapshot_restore.ts +++ b/x-pack/test/api_integration/apis/management/snapshot_restore/snapshot_restore.ts @@ -24,6 +24,8 @@ export default function ({ getService }: FtrProviderContext) { } = registerEsHelpers(getService); describe('Snapshot Lifecycle Management', function () { + this.tags(['skipCloud']); // file system repositories are not supported in cloud + before(async () => { try { await createRepository(REPO_NAME); From ef408068055cd553e2409d5ab0fe686cca695584 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 20 Jan 2021 09:32:42 -0700 Subject: [PATCH 12/83] [Data/Search Sessions] Management UI (#81707) * logging and error handling in session client routes * [Data] Background Search Session Management UI * functional tests * fix ci * new functional tests * fix fn tests * cleanups * cleanup * restore test * configurable refresh and fetch timeout * more tests * feedback items * take expiresSoon field out of the interface * move helper to common * remove bg sessions w/find and delete * add storybook * fix tests * storybook actions * refactor expiration status calculation * isViewable as calculated field * refreshInterval 10s default * list newest first * "Type" => "App" * remove inline view action * in-progress status tooltip shows expire date * move date_string to public * fix tests * Adds management to tsconfig refs * removes preemptive script fix * view action was removed * rename the feature to Search Sessions * Update x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx Co-authored-by: Liza Katz * Update x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx Co-authored-by: Liza Katz * add TODO * use RedirectAppLinks * code review and react improvements * config * fix test * Fix merge * Fix management test * @Dosant code review * code review * Deleteed story * some more code review stuffs * fix ts * Code review and cleanup * Added functional tests for restoring, reloading and canceling a dashboard Renamed search session test service * Don't show expiration time for canceled, expired or errored sessions * fix jest * Moved UISession to public * @tsullivan code review * Fixed import Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Christiane Heiligers Co-authored-by: Liza Katz Co-authored-by: Liza K --- .../public/search/session/sessions_client.ts | 4 +- .../common/search/session/types.ts | 5 +- x-pack/plugins/data_enhanced/config.ts | 6 + x-pack/plugins/data_enhanced/kibana.json | 11 +- x-pack/plugins/data_enhanced/public/plugin.ts | 16 +- .../search/sessions_mgmt/__mocks__/index.tsx | 18 + .../sessions_mgmt/application/index.tsx | 78 + .../sessions_mgmt/application/render.tsx | 39 + .../components/actions/cancel_button.tsx | 89 + .../components/actions/extend_button.tsx | 89 + .../components/actions/get_action.tsx | 53 + .../components/actions/index.tsx | 8 + .../components/actions/popover_actions.tsx | 135 + .../components/actions/reload_button.tsx | 32 + .../sessions_mgmt/components/actions/types.ts | 13 + .../search/sessions_mgmt/components/index.tsx | 37 + .../sessions_mgmt/components/main.test.tsx | 93 + .../search/sessions_mgmt/components/main.tsx | 79 + .../sessions_mgmt/components/status.test.tsx | 132 + .../sessions_mgmt/components/status.tsx | 203 ++ .../components/table/app_filter.tsx | 27 + .../sessions_mgmt/components/table/index.ts | 7 + .../components/table/status_filter.tsx | 31 + .../components/table/table.test.tsx | 192 ++ .../sessions_mgmt/components/table/table.tsx | 122 + .../sessions_mgmt/icons/extend_session.svg | 3 + .../public/search/sessions_mgmt/index.ts | 64 + .../search/sessions_mgmt/lib/api.test.ts | 214 ++ .../public/search/sessions_mgmt/lib/api.ts | 182 ++ .../search/sessions_mgmt/lib/date_string.ts | 22 + .../search/sessions_mgmt/lib/documentation.ts | 22 + .../sessions_mgmt/lib/get_columns.test.tsx | 208 ++ .../search/sessions_mgmt/lib/get_columns.tsx | 233 ++ .../lib/get_expiration_status.ts | 47 + .../public/search/sessions_mgmt/types.ts | 22 + .../search_session_indicator.tsx | 2 +- x-pack/plugins/data_enhanced/server/plugin.ts | 2 +- .../server/routes/session.test.ts | 6 +- .../data_enhanced/server/routes/session.ts | 9 +- .../search/session/session_service.test.ts | 1 + x-pack/plugins/data_enhanced/tsconfig.json | 2 + .../data/search_sessions/data.json.gz | Bin 0 -> 1956 bytes .../data/search_sessions/mappings.json | 2596 +++++++++++++++++ x-pack/test/functional/page_objects/index.ts | 2 + .../search_sessions_management_page.ts | 60 + .../config.ts | 1 + .../services/index.ts | 2 +- .../services/send_to_background.ts | 65 +- .../async_search/send_to_background.ts | 22 +- .../send_to_background_relative_time.ts | 10 +- .../async_search/sessions_in_space.ts | 10 +- .../tests/apps/discover/sessions_in_space.ts | 10 +- .../apps/management/search_sessions/index.ts | 24 + .../search_sessions/sessions_management.ts | 148 + 54 files changed, 5448 insertions(+), 60 deletions(-) create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/cancel_button.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/reload_button.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/index.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/icons/extend_session.svg create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts create mode 100644 x-pack/test/functional/es_archives/data/search_sessions/data.json.gz create mode 100644 x-pack/test/functional/es_archives/data/search_sessions/mappings.json create mode 100644 x-pack/test/functional/page_objects/search_sessions_management_page.ts create mode 100644 x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/index.ts create mode 100644 x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts diff --git a/src/plugins/data/public/search/session/sessions_client.ts b/src/plugins/data/public/search/session/sessions_client.ts index f4ad2df530d12..5b0ba51c2f344 100644 --- a/src/plugins/data/public/search/session/sessions_client.ts +++ b/src/plugins/data/public/search/session/sessions_client.ts @@ -56,8 +56,8 @@ export class SessionsClient { }); } - public find(options: SavedObjectsFindOptions): Promise { - return this.http!.post(`/internal/session`, { + public find(options: Omit): Promise { + return this.http!.post(`/internal/session/_find`, { body: JSON.stringify(options), }); } diff --git a/x-pack/plugins/data_enhanced/common/search/session/types.ts b/x-pack/plugins/data_enhanced/common/search/session/types.ts index ada7988c31f30..9eefdf43aa245 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/types.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SearchSessionStatus } from './'; + export interface SearchSessionSavedObjectAttributes { /** * User-facing session name to be displayed in session management @@ -24,7 +26,7 @@ export interface SearchSessionSavedObjectAttributes { /** * status */ - status: string; + status: SearchSessionStatus; /** * urlGeneratorId */ @@ -44,7 +46,6 @@ export interface SearchSessionSavedObjectAttributes { */ idMapping: Record; } - export interface SearchSessionRequestInfo { /** * ID of the async search request diff --git a/x-pack/plugins/data_enhanced/config.ts b/x-pack/plugins/data_enhanced/config.ts index 4c90b1fb4c81d..981c398019832 100644 --- a/x-pack/plugins/data_enhanced/config.ts +++ b/x-pack/plugins/data_enhanced/config.ts @@ -15,6 +15,12 @@ export const configSchema = schema.object({ inMemTimeout: schema.duration({ defaultValue: '1m' }), maxUpdateRetries: schema.number({ defaultValue: 3 }), defaultExpiration: schema.duration({ defaultValue: '7d' }), + management: schema.object({ + maxSessions: schema.number({ defaultValue: 10000 }), + refreshInterval: schema.duration({ defaultValue: '10s' }), + refreshTimeout: schema.duration({ defaultValue: '1m' }), + expiresSoonWarning: schema.duration({ defaultValue: '1d' }), + }), }), }), }); diff --git a/x-pack/plugins/data_enhanced/kibana.json b/x-pack/plugins/data_enhanced/kibana.json index 3951468f6e569..037f52fcb4b05 100644 --- a/x-pack/plugins/data_enhanced/kibana.json +++ b/x-pack/plugins/data_enhanced/kibana.json @@ -2,15 +2,8 @@ "id": "dataEnhanced", "version": "8.0.0", "kibanaVersion": "kibana", - "configPath": [ - "xpack", "data_enhanced" - ], - "requiredPlugins": [ - "bfetch", - "data", - "features", - "taskManager" - ], + "configPath": ["xpack", "data_enhanced"], + "requiredPlugins": ["bfetch", "data", "features", "management", "share", "taskManager"], "optionalPlugins": ["kibanaUtils", "usageCollection"], "server": true, "ui": true, diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index fed2b4e71ab50..add7a966fee34 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -8,10 +8,13 @@ import React from 'react'; import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { BfetchPublicSetup } from '../../../../src/plugins/bfetch/public'; +import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { SharePluginStart } from '../../../../src/plugins/share/public'; import { setAutocompleteService } from './services'; import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; import { EnhancedSearchInterceptor } from './search/search_interceptor'; +import { registerSearchSessionsMgmt } from './search/sessions_mgmt'; import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; import { createConnectedSearchSessionIndicator } from './search'; import { ConfigSchema } from '../config'; @@ -19,9 +22,11 @@ import { ConfigSchema } from '../config'; export interface DataEnhancedSetupDependencies { bfetch: BfetchPublicSetup; data: DataPublicPluginSetup; + management: ManagementSetup; } export interface DataEnhancedStartDependencies { data: DataPublicPluginStart; + share: SharePluginStart; } export type DataEnhancedSetup = ReturnType; @@ -30,12 +35,13 @@ export type DataEnhancedStart = ReturnType; export class DataEnhancedPlugin implements Plugin { private enhancedSearchInterceptor!: EnhancedSearchInterceptor; + private config!: ConfigSchema; constructor(private initializerContext: PluginInitializerContext) {} public setup( core: CoreSetup, - { bfetch, data }: DataEnhancedSetupDependencies + { bfetch, data, management }: DataEnhancedSetupDependencies ) { data.autocomplete.addQuerySuggestionProvider( KUERY_LANGUAGE_NAME, @@ -57,12 +63,18 @@ export class DataEnhancedPlugin searchInterceptor: this.enhancedSearchInterceptor, }, }); + + this.config = this.initializerContext.config.get(); + if (this.config.search.sessions.enabled) { + const { management: sessionsMgmtConfig } = this.config.search.sessions; + registerSearchSessionsMgmt(core, sessionsMgmtConfig, { management }); + } } public start(core: CoreStart, plugins: DataEnhancedStartDependencies) { setAutocompleteService(plugins.data.autocomplete); - if (this.initializerContext.config.get().search.sessions.enabled) { + if (this.config.search.sessions.enabled) { core.chrome.setBreadcrumbsAppendExtension({ content: toMountPoint( React.createElement( diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx new file mode 100644 index 0000000000000..e9fc8e6ac6bf9 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.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, { ReactNode } from 'react'; +import { IntlProvider } from 'react-intl'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { UrlGeneratorsStart } from '../../../../../../../src/plugins/share/public/url_generators'; + +export function LocaleWrapper({ children }: { children?: ReactNode }) { + return {children}; +} + +export const mockUrls = ({ + getUrlGenerator: (id: string) => ({ createUrl: () => `hello-cool-${id}-url` }), +} as unknown) as UrlGeneratorsStart; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx new file mode 100644 index 0000000000000..27f1482a4d20d --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup } from 'kibana/public'; +import type { ManagementAppMountParams } from 'src/plugins/management/public'; +import type { + AppDependencies, + IManagementSectionsPluginsSetup, + IManagementSectionsPluginsStart, + SessionsMgmtConfigSchema, +} from '../'; +import { APP } from '../'; +import { SearchSessionsMgmtAPI } from '../lib/api'; +import { AsyncSearchIntroDocumentation } from '../lib/documentation'; +import { renderApp } from './render'; + +export class SearchSessionsMgmtApp { + constructor( + private coreSetup: CoreSetup, + private config: SessionsMgmtConfigSchema, + private params: ManagementAppMountParams, + private pluginsSetup: IManagementSectionsPluginsSetup + ) {} + + public async mountManagementSection() { + const { coreSetup, params, pluginsSetup } = this; + const [coreStart, pluginsStart] = await coreSetup.getStartServices(); + + const { + chrome: { docTitle }, + http, + docLinks, + i18n, + notifications, + uiSettings, + application, + } = coreStart; + const { data, share } = pluginsStart; + + const pluginName = APP.getI18nName(); + docTitle.change(pluginName); + params.setBreadcrumbs([{ text: pluginName }]); + + const { sessionsClient } = data.search; + const api = new SearchSessionsMgmtAPI(sessionsClient, this.config, { + notifications, + urls: share.urlGenerators, + application, + }); + + const documentation = new AsyncSearchIntroDocumentation(docLinks); + + const dependencies: AppDependencies = { + plugins: pluginsSetup, + config: this.config, + documentation, + core: coreStart, + api, + http, + i18n, + uiSettings, + share, + }; + + const { element } = params; + const unmountAppCb = renderApp(element, dependencies); + + return () => { + docTitle.reset(); + unmountAppCb(); + }; + } +} + +export { renderApp }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx new file mode 100644 index 0000000000000..f5ee35fcff9a9 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.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 { render, unmountComponentAtNode } from 'react-dom'; +import { AppDependencies } from '../'; +import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; +import { SearchSessionsMgmtMain } from '../components/main'; + +export const renderApp = ( + elem: HTMLElement | null, + { i18n, uiSettings, ...homeDeps }: AppDependencies +) => { + if (!elem) { + return () => undefined; + } + + const { Context: I18nContext } = i18n; + // uiSettings is required by the listing table to format dates in the timezone from Settings + const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ + uiSettings, + }); + + render( + + + + + , + elem + ); + + return () => { + unmountComponentAtNode(elem); + }; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/cancel_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/cancel_button.tsx new file mode 100644 index 0000000000000..8f4c8845de235 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/cancel_button.tsx @@ -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 { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useState } from 'react'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { TableText } from '../'; +import { OnActionComplete } from './types'; + +interface CancelButtonProps { + id: string; + name: string; + api: SearchSessionsMgmtAPI; + onActionComplete: OnActionComplete; +} + +const CancelConfirm = ({ + onConfirmDismiss, + ...props +}: CancelButtonProps & { onConfirmDismiss: () => void }) => { + const { id, name, api, onActionComplete } = props; + const [isLoading, setIsLoading] = useState(false); + + const title = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.title', { + defaultMessage: 'Cancel search session', + }); + const confirm = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.cancelButton', { + defaultMessage: 'Cancel', + }); + const cancel = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.dontCancelButton', { + defaultMessage: 'Dismiss', + }); + const message = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.message', { + defaultMessage: `Canceling the search session \'{name}\' will expire any cached results, so that quick restore will no longer be available. You will still be able to re-run it, using the reload action.`, + values: { + name, + }, + }); + + return ( + + { + setIsLoading(true); + await api.sendCancel(id); + onActionComplete(); + }} + confirmButtonText={confirm} + confirmButtonDisabled={isLoading} + cancelButtonText={cancel} + defaultFocusedButton="confirm" + buttonColor="danger" + > + {message} + + + ); +}; + +export const CancelButton = (props: CancelButtonProps) => { + const [showConfirm, setShowConfirm] = useState(false); + + const onClick = () => { + setShowConfirm(true); + }; + + const onConfirmDismiss = () => { + setShowConfirm(false); + }; + + return ( + <> + + + + {showConfirm ? : null} + + ); +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx new file mode 100644 index 0000000000000..4c8a7b0217688 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx @@ -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 { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useState } from 'react'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { TableText } from '../'; +import { OnActionComplete } from './types'; + +interface ExtendButtonProps { + id: string; + name: string; + api: SearchSessionsMgmtAPI; + onActionComplete: OnActionComplete; +} + +const ExtendConfirm = ({ + onConfirmDismiss, + ...props +}: ExtendButtonProps & { onConfirmDismiss: () => void }) => { + const { id, name, api, onActionComplete } = props; + const [isLoading, setIsLoading] = useState(false); + + const title = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.title', { + defaultMessage: 'Extend search session expiration', + }); + const confirm = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.extendButton', { + defaultMessage: 'Extend', + }); + const extend = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.dontExtendButton', { + defaultMessage: 'Cancel', + }); + const message = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.extendMessage', { + defaultMessage: "When would you like the search session '{name}' to expire?", + values: { + name, + }, + }); + + return ( + + { + setIsLoading(true); + await api.sendExtend(id, '1'); + onActionComplete(); + }} + confirmButtonText={confirm} + confirmButtonDisabled={isLoading} + cancelButtonText={extend} + defaultFocusedButton="confirm" + buttonColor="primary" + > + {message} + + + ); +}; + +export const ExtendButton = (props: ExtendButtonProps) => { + const [showConfirm, setShowConfirm] = useState(false); + + const onClick = () => { + setShowConfirm(true); + }; + + const onConfirmDismiss = () => { + setShowConfirm(false); + }; + + return ( + <> + + + + {showConfirm ? : null} + + ); +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx new file mode 100644 index 0000000000000..5bf0fbda5b5cc --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { IClickActionDescriptor } from '../'; +import extendSessionIcon from '../../icons/extend_session.svg'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { UISession } from '../../types'; +import { CancelButton } from './cancel_button'; +import { ExtendButton } from './extend_button'; +import { ReloadButton } from './reload_button'; +import { ACTION, OnActionComplete } from './types'; + +export const getAction = ( + api: SearchSessionsMgmtAPI, + actionType: string, + { id, name, reloadUrl }: UISession, + onActionComplete: OnActionComplete +): IClickActionDescriptor | null => { + switch (actionType) { + case ACTION.CANCEL: + return { + iconType: 'crossInACircleFilled', + textColor: 'default', + label: , + }; + + case ACTION.RELOAD: + return { + iconType: 'refresh', + textColor: 'default', + label: , + }; + + case ACTION.EXTEND: + return { + iconType: extendSessionIcon, + textColor: 'default', + label: , + }; + + default: + // eslint-disable-next-line no-console + console.error(`Unknown action: ${actionType}`); + } + + // Unknown action: do not show + + return null; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx new file mode 100644 index 0000000000000..82b4d84aa7ea2 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { PopoverActionsMenu } from './popover_actions'; +export * from './types'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx new file mode 100644 index 0000000000000..b9b915c0b17cb --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -0,0 +1,135 @@ +/* + * 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 { + EuiButtonIcon, + EuiContextMenu, + EuiContextMenuPanelDescriptor, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPopover, + EuiTextProps, + EuiToolTip, +} from '@elastic/eui'; +import { + EuiContextMenuPanelItemDescriptorEntry, + EuiContextMenuPanelItemSeparator, +} from '@elastic/eui/src/components/context_menu/context_menu'; +import { i18n } from '@kbn/i18n'; +import React, { ReactElement, useState } from 'react'; +import { TableText } from '../'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { UISession } from '../../types'; +import { getAction } from './get_action'; +import { ACTION, OnActionComplete } from './types'; + +// interfaces +interface PopoverActionProps { + textColor?: EuiTextProps['color']; + iconType: string; + children: string | ReactElement; +} + +interface PopoverActionItemsProps { + session: UISession; + api: SearchSessionsMgmtAPI; + onActionComplete: OnActionComplete; +} + +// helper +const PopoverAction = ({ textColor, iconType, children, ...props }: PopoverActionProps) => ( + + + + + + {children} + + +); + +export const PopoverActionsMenu = ({ api, onActionComplete, session }: PopoverActionItemsProps) => { + const [isPopoverOpen, setPopover] = useState(false); + + const onPopoverClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const renderPopoverButton = () => ( + + + + ); + + const actions = session.actions || []; + // Generic set of actions - up to the API to return what is available + const items = actions.reduce((itemSet, actionType) => { + const actionDef = getAction(api, actionType, session, onActionComplete); + if (actionDef) { + const { label, textColor, iconType } = actionDef; + + // add a line above the delete action (when there are multiple) + // NOTE: Delete action MUST be the final action[] item + if (actions.length > 1 && actionType === ACTION.CANCEL) { + itemSet.push({ isSeparator: true, key: 'separadorable' }); + } + + return [ + ...itemSet, + { + key: `action-${actionType}`, + name: ( + + {label} + + ), + }, + ]; + } + return itemSet; + }, [] as Array); + + const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, items }]; + + return actions.length ? ( + + + + ) : null; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/reload_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/reload_button.tsx new file mode 100644 index 0000000000000..9a98ab2044770 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/reload_button.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { TableText } from '../'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; + +interface ReloadButtonProps { + api: SearchSessionsMgmtAPI; + reloadUrl: string; +} + +export const ReloadButton = (props: ReloadButtonProps) => { + function onClick() { + props.api.reloadSearchSession(props.reloadUrl); + } + + return ( + <> + + + + + ); +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts new file mode 100644 index 0000000000000..4b81fd7fda9a0 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type OnActionComplete = () => void; + +export enum ACTION { + EXTEND = 'extend', + CANCEL = 'cancel', + RELOAD = 'reload', +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx new file mode 100644 index 0000000000000..ffb0992469a8a --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiLinkProps, EuiText, EuiTextProps } from '@elastic/eui'; +import React from 'react'; +import extendSessionIcon from '../icons/extend_session.svg'; + +export { OnActionComplete, PopoverActionsMenu } from './actions'; + +export const TableText = ({ children, ...props }: EuiTextProps) => { + return ( + + {children} + + ); +}; + +export interface IClickActionDescriptor { + label: string | React.ReactElement; + iconType: 'trash' | 'cancel' | typeof extendSessionIcon; + textColor: EuiTextProps['color']; +} + +export interface IHrefActionDescriptor { + label: string; + props: EuiLinkProps; +} + +export interface StatusDef { + textColor?: EuiTextProps['color']; + icon?: React.ReactElement; + label: React.ReactElement; + toolTipContent: string; +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx new file mode 100644 index 0000000000000..e01d1a28c5e54 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MockedKeys } from '@kbn/utility-types/jest'; +import { mount, ReactWrapper } from 'enzyme'; +import { CoreSetup, CoreStart, DocLinksStart } from 'kibana/public'; +import moment from 'moment'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { coreMock } from 'src/core/public/mocks'; +import { SessionsClient } from 'src/plugins/data/public/search'; +import { SessionsMgmtConfigSchema } from '..'; +import { SearchSessionsMgmtAPI } from '../lib/api'; +import { AsyncSearchIntroDocumentation } from '../lib/documentation'; +import { LocaleWrapper, mockUrls } from '../__mocks__'; +import { SearchSessionsMgmtMain } from './main'; + +let mockCoreSetup: MockedKeys; +let mockCoreStart: MockedKeys; +let mockConfig: SessionsMgmtConfigSchema; +let sessionsClient: SessionsClient; +let api: SearchSessionsMgmtAPI; + +describe('Background Search Session Management Main', () => { + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + mockCoreStart = coreMock.createStart(); + mockConfig = { + expiresSoonWarning: moment.duration(1, 'days'), + maxSessions: 2000, + refreshInterval: moment.duration(1, 'seconds'), + refreshTimeout: moment.duration(10, 'minutes'), + }; + + sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); + + api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + }); + + describe('renders', () => { + const docLinks: DocLinksStart = { + ELASTIC_WEBSITE_URL: 'boo/', + DOC_LINK_VERSION: '#foo', + links: {} as any, + }; + + let main: ReactWrapper; + + beforeEach(async () => { + mockCoreSetup.uiSettings.get.mockImplementation((key: string) => { + return key === 'dateFormat:tz' ? 'UTC' : null; + }); + + await act(async () => { + main = mount( + + + + ); + }); + }); + + test('page title', () => { + expect(main.find('h1').text()).toBe('Search Sessions'); + }); + + test('documentation link', () => { + const docLink = main.find('a[href]').first(); + expect(docLink.text()).toBe('Documentation'); + expect(docLink.prop('href')).toBe( + 'boo/guide/en/elasticsearch/reference/#foo/async-search-intro.html' + ); + }); + + test('table is present', () => { + expect(main.find(`[data-test-subj="search-sessions-mgmt-table"]`).exists()).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx new file mode 100644 index 0000000000000..80c6a580dd183 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx @@ -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 { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiPageBody, + EuiPageContent, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { CoreStart, HttpStart } from 'kibana/public'; +import React from 'react'; +import type { SessionsMgmtConfigSchema } from '../'; +import type { SearchSessionsMgmtAPI } from '../lib/api'; +import type { AsyncSearchIntroDocumentation } from '../lib/documentation'; +import { TableText } from './'; +import { SearchSessionsMgmtTable } from './table'; + +interface Props { + documentation: AsyncSearchIntroDocumentation; + core: CoreStart; + api: SearchSessionsMgmtAPI; + http: HttpStart; + timezone: string; + config: SessionsMgmtConfigSchema; +} + +export function SearchSessionsMgmtMain({ documentation, ...tableProps }: Props) { + return ( + + + + + +

+ +

+ + + + + + + + + + +

+ +

+
+ + + + + + + ); +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx new file mode 100644 index 0000000000000..706001ac42146 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.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 { EuiTextProps, EuiToolTipProps } from '@elastic/eui'; +import { mount } from 'enzyme'; +import React from 'react'; +import { SearchSessionStatus } from '../../../../common/search'; +import { UISession } from '../types'; +import { LocaleWrapper } from '../__mocks__'; +import { getStatusText, StatusIndicator } from './status'; + +let tz: string; +let session: UISession; + +const mockNowTime = new Date(); +mockNowTime.setTime(1607026176061); + +describe('Background Search Session management status labels', () => { + beforeEach(() => { + tz = 'Browser'; + session = { + name: 'amazing test', + id: 'wtywp9u2802hahgp-gsla', + restoreUrl: '/app/great-app-url/#45', + reloadUrl: '/app/great-app-url/#45', + appId: 'security', + status: SearchSessionStatus.IN_PROGRESS, + created: '2020-12-02T00:19:32Z', + expires: '2020-12-07T00:19:32Z', + }; + }); + + describe('getStatusText', () => { + test('in progress', () => { + expect(getStatusText(SearchSessionStatus.IN_PROGRESS)).toBe('In progress'); + }); + test('expired', () => { + expect(getStatusText(SearchSessionStatus.EXPIRED)).toBe('Expired'); + }); + test('cancelled', () => { + expect(getStatusText(SearchSessionStatus.CANCELLED)).toBe('Cancelled'); + }); + test('complete', () => { + expect(getStatusText(SearchSessionStatus.COMPLETE)).toBe('Complete'); + }); + test('error', () => { + expect(getStatusText('error')).toBe('Error'); + }); + }); + + describe('StatusIndicator', () => { + test('render in progress', () => { + const statusIndicator = mount( + + + + ); + + const label = statusIndicator.find( + `.euiText[data-test-subj="sessionManagementStatusLabel"][data-test-status="in_progress"]` + ); + expect(label.text()).toMatchInlineSnapshot(`"In progress"`); + }); + + test('complete', () => { + session.status = SearchSessionStatus.COMPLETE; + + const statusIndicator = mount( + + + + ); + + const label = statusIndicator + .find(`[data-test-subj="sessionManagementStatusLabel"][data-test-status="complete"]`) + .first(); + expect((label.props() as EuiTextProps).color).toBe('secondary'); + expect(label.text()).toBe('Complete'); + }); + + test('complete - expires soon', () => { + session.status = SearchSessionStatus.COMPLETE; + + const statusIndicator = mount( + + + + ); + + const tooltip = statusIndicator.find('EuiToolTip'); + expect((tooltip.first().props() as EuiToolTipProps).content).toMatchInlineSnapshot( + `"Expires on 6 Dec, 2020, 19:19:32"` + ); + }); + + test('expired', () => { + session.status = SearchSessionStatus.EXPIRED; + + const statusIndicator = mount( + + + + ); + + const label = statusIndicator + .find(`[data-test-subj="sessionManagementStatusLabel"][data-test-status="expired"]`) + .first(); + expect(label.text()).toBe('Expired'); + }); + + test('error handling', () => { + session.status = SearchSessionStatus.COMPLETE; + (session as any).created = null; + (session as any).expires = null; + + const statusIndicator = mount( + + + + ); + + // no unhandled errors + const tooltip = statusIndicator.find('EuiToolTip'); + expect((tooltip.first().props() as EuiToolTipProps).content).toMatchInlineSnapshot( + `"Expires on unknown"` + ); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx new file mode 100644 index 0000000000000..8e0946c140287 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -0,0 +1,203 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { ReactElement } from 'react'; +import { SearchSessionStatus } from '../../../../common/search'; +import { dateString } from '../lib/date_string'; +import { UISession } from '../types'; +import { StatusDef as StatusAttributes, TableText } from './'; + +// Shared helper function +export const getStatusText = (statusType: string): string => { + switch (statusType) { + case SearchSessionStatus.IN_PROGRESS: + return i18n.translate('xpack.data.mgmt.searchSessions.status.label.inProgress', { + defaultMessage: 'In progress', + }); + case SearchSessionStatus.EXPIRED: + return i18n.translate('xpack.data.mgmt.searchSessions.status.label.expired', { + defaultMessage: 'Expired', + }); + case SearchSessionStatus.CANCELLED: + return i18n.translate('xpack.data.mgmt.searchSessions.status.label.cancelled', { + defaultMessage: 'Cancelled', + }); + case SearchSessionStatus.COMPLETE: + return i18n.translate('xpack.data.mgmt.searchSessions.status.label.complete', { + defaultMessage: 'Complete', + }); + case SearchSessionStatus.ERROR: + return i18n.translate('xpack.data.mgmt.searchSessions.status.label.error', { + defaultMessage: 'Error', + }); + default: + // eslint-disable-next-line no-console + console.error(`Unknown status ${statusType}`); + return statusType; + } +}; + +interface StatusIndicatorProps { + now?: string; + session: UISession; + timezone: string; +} + +// Get the fields needed to show each status type +// can throw errors around date conversions +const getStatusAttributes = ({ + now, + session, + timezone, +}: StatusIndicatorProps): StatusAttributes | null => { + let expireDate: string; + if (session.expires) { + expireDate = dateString(session.expires!, timezone); + } else { + expireDate = i18n.translate('xpack.data.mgmt.searchSessions.status.expireDateUnknown', { + defaultMessage: 'unknown', + }); + } + + switch (session.status) { + case SearchSessionStatus.IN_PROGRESS: + try { + return { + textColor: 'default', + icon: , + label: {getStatusText(session.status)}, + toolTipContent: i18n.translate( + 'xpack.data.mgmt.searchSessions.status.message.createdOn', + { + defaultMessage: 'Expires on {expireDate}', + values: { expireDate }, + } + ), + }; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + throw new Error(`Could not instantiate a createdDate object from: ${session.created}`); + } + + case SearchSessionStatus.EXPIRED: + try { + const toolTipContent = i18n.translate( + 'xpack.data.mgmt.searchSessions.status.message.expiredOn', + { + defaultMessage: 'Expired on {expireDate}', + values: { expireDate }, + } + ); + + return { + icon: , + label: {getStatusText(session.status)}, + toolTipContent, + }; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + throw new Error(`Could not instantiate an expiration Date object from: ${session.expires}`); + } + + case SearchSessionStatus.CANCELLED: + return { + icon: , + label: {getStatusText(session.status)}, + toolTipContent: i18n.translate('xpack.data.mgmt.searchSessions.status.message.cancelled', { + defaultMessage: 'Cancelled by user', + }), + }; + + case SearchSessionStatus.ERROR: + return { + textColor: 'danger', + icon: , + label: {getStatusText(session.status)}, + toolTipContent: i18n.translate('xpack.data.mgmt.searchSessions.status.message.error', { + defaultMessage: 'Error: {error}', + values: { error: (session as any).error || 'unknown' }, + }), + }; + + case SearchSessionStatus.COMPLETE: + try { + const toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresOn', { + defaultMessage: 'Expires on {expireDate}', + values: { expireDate }, + }); + + return { + textColor: 'secondary', + icon: , + label: {getStatusText(session.status)}, + toolTipContent, + }; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + throw new Error( + `Could not instantiate an expiration Date object for completed session from: ${session.expires}` + ); + } + + // Error was thrown + return null; + + default: + throw new Error(`Unknown status: ${session.status}`); + } +}; + +export const StatusIndicator = (props: StatusIndicatorProps) => { + try { + const statusDef = getStatusAttributes(props); + const { session } = props; + + if (statusDef) { + const { toolTipContent } = statusDef; + let icon: ReactElement | undefined = statusDef.icon; + let label: ReactElement = statusDef.label; + + if (icon && toolTipContent) { + icon = {icon}; + } + if (toolTipContent) { + label = ( + + + {statusDef.label} + + + ); + } + + return ( + + {icon} + + + {label} + + + + ); + } + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + } + + // Exception has been caught + return {props.session.status}; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx new file mode 100644 index 0000000000000..236fc492031c0 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { capitalize } from 'lodash'; +import { UISession } from '../../types'; + +export const getAppFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({ + type: 'field_value_selection', + name: i18n.translate('xpack.data.mgmt.searchSessions.search.filterApp', { + defaultMessage: 'App', + }), + field: 'appId', + multiSelect: 'or', + options: tableData.reduce((options: FieldValueOptionType[], { appId }) => { + const existingOption = options.find((o) => o.value === appId); + if (!existingOption) { + return [...options, { value: appId, view: capitalize(appId) }]; + } + + return options; + }, []), +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/index.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/index.ts new file mode 100644 index 0000000000000..83ca1c223dfc4 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/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 { SearchSessionsMgmtTable } from './table'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx new file mode 100644 index 0000000000000..04421ad66e588 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { TableText } from '../'; +import { UISession } from '../../types'; +import { getStatusText } from '../status'; + +export const getStatusFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({ + type: 'field_value_selection', + name: i18n.translate('xpack.data.mgmt.searchSessions.search.filterStatus', { + defaultMessage: 'Status', + }), + field: 'status', + multiSelect: 'or', + options: tableData.reduce((options: FieldValueOptionType[], session) => { + const { status: statusType } = session; + const existingOption = options.find((o) => o.value === statusType); + if (!existingOption) { + const view = {getStatusText(session.status)}; + return [...options, { value: statusType, view }]; + } + + return options; + }, []), +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx new file mode 100644 index 0000000000000..357f17649394b --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -0,0 +1,192 @@ +/* + * 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 { MockedKeys } from '@kbn/utility-types/jest'; +import { act, waitFor } from '@testing-library/react'; +import { mount, ReactWrapper } from 'enzyme'; +import { CoreSetup, CoreStart } from 'kibana/public'; +import moment from 'moment'; +import React from 'react'; +import { coreMock } from 'src/core/public/mocks'; +import { SessionsClient } from 'src/plugins/data/public/search'; +import { SearchSessionStatus } from '../../../../../common/search'; +import { SessionsMgmtConfigSchema } from '../../'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { LocaleWrapper, mockUrls } from '../../__mocks__'; +import { SearchSessionsMgmtTable } from './table'; + +let mockCoreSetup: MockedKeys; +let mockCoreStart: CoreStart; +let mockConfig: SessionsMgmtConfigSchema; +let sessionsClient: SessionsClient; +let api: SearchSessionsMgmtAPI; + +describe('Background Search Session Management Table', () => { + beforeEach(async () => { + mockCoreSetup = coreMock.createSetup(); + mockCoreStart = coreMock.createStart(); + mockConfig = { + expiresSoonWarning: moment.duration(1, 'days'), + maxSessions: 2000, + refreshInterval: moment.duration(1, 'seconds'), + refreshTimeout: moment.duration(10, 'minutes'), + }; + + sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); + api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + }); + + describe('renders', () => { + let table: ReactWrapper; + + const getInitialResponse = () => { + return { + saved_objects: [ + { + id: 'wtywp9u2802hahgp-flps', + attributes: { + name: 'very background search', + id: 'wtywp9u2802hahgp-flps', + url: '/app/great-app-url/#48', + appId: 'canvas', + status: SearchSessionStatus.IN_PROGRESS, + created: '2020-12-02T00:19:32Z', + expires: '2020-12-07T00:19:32Z', + }, + }, + ], + }; + }; + + test('table header cells', async () => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return getInitialResponse(); + }); + + await act(async () => { + table = mount( + + + + ); + }); + + expect(table.find('thead th').map((node) => node.text())).toMatchInlineSnapshot(` + Array [ + "AppClick to sort in ascending order", + "NameClick to sort in ascending order", + "StatusClick to sort in ascending order", + "CreatedClick to unsort", + "ExpirationClick to sort in ascending order", + ] + `); + }); + + test('table body cells', async () => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return getInitialResponse(); + }); + + await act(async () => { + table = mount( + + + + ); + }); + table.update(); + + expect(table.find('tbody td').map((node) => node.text())).toMatchInlineSnapshot(` + Array [ + "App", + "Namevery background search", + "StatusIn progress", + "Created2 Dec, 2020, 00:19:32", + "Expiration7 Dec, 2020, 00:19:32", + "", + "", + ] + `); + }); + }); + + describe('fetching sessions data', () => { + test('re-fetches data', async () => { + jest.useFakeTimers(); + sessionsClient.find = jest.fn(); + mockConfig = { + ...mockConfig, + refreshInterval: moment.duration(10, 'seconds'), + }; + + await act(async () => { + mount( + + + + ); + jest.advanceTimersByTime(20000); + }); + + // 1 for initial load + 2 refresh calls + expect(sessionsClient.find).toBeCalledTimes(3); + + jest.useRealTimers(); + }); + + test('refresh button uses the session client', async () => { + sessionsClient.find = jest.fn(); + + mockConfig = { + ...mockConfig, + refreshInterval: moment.duration(1, 'day'), + refreshTimeout: moment.duration(2, 'days'), + }; + + await act(async () => { + const table = mount( + + + + ); + + const buttonSelector = `[data-test-subj="sessionManagementRefreshBtn"] button`; + + await waitFor(() => { + table.find(buttonSelector).first().simulate('click'); + table.update(); + }); + }); + + // initial call + click + expect(sessionsClient.find).toBeCalledTimes(2); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx new file mode 100644 index 0000000000000..f7aecdbd58a23 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -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 { EuiButton, EuiInMemoryTable, EuiSearchBarProps } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { CoreStart } from 'kibana/public'; +import moment from 'moment'; +import React, { useCallback, useMemo, useRef, useEffect, useState } from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; +import useInterval from 'react-use/lib/useInterval'; +import { TableText } from '../'; +import { SessionsMgmtConfigSchema } from '../..'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { getColumns } from '../../lib/get_columns'; +import { UISession } from '../../types'; +import { OnActionComplete } from '../actions'; +import { getAppFilter } from './app_filter'; +import { getStatusFilter } from './status_filter'; + +const TABLE_ID = 'searchSessionsMgmtTable'; + +interface Props { + core: CoreStart; + api: SearchSessionsMgmtAPI; + timezone: string; + config: SessionsMgmtConfigSchema; +} + +export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props }: Props) { + const [tableData, setTableData] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [debouncedIsLoading, setDebouncedIsLoading] = useState(false); + const [pagination, setPagination] = useState({ pageIndex: 0 }); + const showLatestResultsHandler = useRef(); + const refreshInterval = useMemo(() => moment.duration(config.refreshInterval).asMilliseconds(), [ + config.refreshInterval, + ]); + + // Debounce rendering the state of the Refresh button + useDebounce( + () => { + setDebouncedIsLoading(isLoading); + }, + 250, + [isLoading] + ); + + // refresh behavior + const doRefresh = useCallback(async () => { + setIsLoading(true); + const renderResults = (results: UISession[]) => { + setTableData(results); + }; + showLatestResultsHandler.current = renderResults; + let results: UISession[] = []; + try { + results = await api.fetchTableData(); + } catch (e) {} // eslint-disable-line no-empty + + if (showLatestResultsHandler.current === renderResults) { + renderResults(results); + setIsLoading(false); + } + }, [api]); + + // initial data load + useEffect(() => { + doRefresh(); + }, [doRefresh]); + + useInterval(doRefresh, refreshInterval); + + const onActionComplete: OnActionComplete = () => { + doRefresh(); + }; + + // table config: search / filters + const search: EuiSearchBarProps = { + box: { incremental: true }, + filters: [getStatusFilter(tableData), getAppFilter(tableData)], + toolsRight: ( + + + + + + ), + }; + + return ( + + {...props} + id={TABLE_ID} + data-test-subj={TABLE_ID} + rowProps={() => ({ + 'data-test-subj': 'searchSessionsRow', + })} + columns={getColumns(core, api, config, timezone, onActionComplete)} + items={tableData} + pagination={pagination} + search={search} + sorting={{ sort: { field: 'created', direction: 'desc' } }} + onTableChange={({ page: { index } }) => { + setPagination({ pageIndex: index }); + }} + tableLayout="auto" + /> + ); +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/icons/extend_session.svg b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/icons/extend_session.svg new file mode 100644 index 0000000000000..7cb9f7e6a24c2 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/icons/extend_session.svg @@ -0,0 +1,3 @@ + + + diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts new file mode 100644 index 0000000000000..76a5d440cd898 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import type { CoreStart, HttpStart, I18nStart, IUiSettingsClient } from 'kibana/public'; +import { CoreSetup } from 'kibana/public'; +import type { DataPublicPluginStart } from 'src/plugins/data/public'; +import type { ManagementSetup } from 'src/plugins/management/public'; +import type { SharePluginStart } from 'src/plugins/share/public'; +import type { ConfigSchema } from '../../../config'; +import type { DataEnhancedStartDependencies } from '../../plugin'; +import type { SearchSessionsMgmtAPI } from './lib/api'; +import type { AsyncSearchIntroDocumentation } from './lib/documentation'; + +export interface IManagementSectionsPluginsSetup { + management: ManagementSetup; +} + +export interface IManagementSectionsPluginsStart { + data: DataPublicPluginStart; + share: SharePluginStart; +} + +export interface AppDependencies { + plugins: IManagementSectionsPluginsSetup; + share: SharePluginStart; + uiSettings: IUiSettingsClient; + documentation: AsyncSearchIntroDocumentation; + core: CoreStart; // for RedirectAppLinks + api: SearchSessionsMgmtAPI; + http: HttpStart; + i18n: I18nStart; + config: SessionsMgmtConfigSchema; +} + +export const APP = { + id: 'search_sessions', + getI18nName: (): string => + i18n.translate('xpack.data.mgmt.searchSessions.appTitle', { + defaultMessage: 'Search Sessions', + }), +}; + +export type SessionsMgmtConfigSchema = ConfigSchema['search']['sessions']['management']; + +export function registerSearchSessionsMgmt( + coreSetup: CoreSetup, + config: SessionsMgmtConfigSchema, + services: IManagementSectionsPluginsSetup +) { + services.management.sections.section.kibana.registerApp({ + id: APP.id, + title: APP.getI18nName(), + order: 2, + mount: async (params) => { + const { SearchSessionsMgmtApp: MgmtApp } = await import('./application'); + const mgmtApp = new MgmtApp(coreSetup, config, params, services); + return mgmtApp.mountManagementSection(); + }, + }); +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts new file mode 100644 index 0000000000000..5b337dfd03eb1 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -0,0 +1,214 @@ +/* + * 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 { MockedKeys } from '@kbn/utility-types/jest'; +import { CoreSetup, CoreStart } from 'kibana/public'; +import moment from 'moment'; +import { coreMock } from 'src/core/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import type { SavedObjectsFindResponse } from 'src/core/server'; +import { SessionsClient } from 'src/plugins/data/public/search'; +import type { SessionsMgmtConfigSchema } from '../'; +import { SearchSessionStatus } from '../../../../common/search'; +import { mockUrls } from '../__mocks__'; +import { SearchSessionsMgmtAPI } from './api'; + +let mockCoreSetup: MockedKeys; +let mockCoreStart: MockedKeys; +let mockConfig: SessionsMgmtConfigSchema; +let sessionsClient: SessionsClient; + +describe('Search Sessions Management API', () => { + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + mockCoreStart = coreMock.createStart(); + mockConfig = { + expiresSoonWarning: moment.duration('1d'), + maxSessions: 2000, + refreshInterval: moment.duration('1s'), + refreshTimeout: moment.duration('10m'), + }; + + sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); + }); + + describe('listing', () => { + test('fetchDataTable calls the listing endpoint', async () => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return { + saved_objects: [ + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie', appId: 'pizza', status: 'complete' }, + }, + ], + } as SavedObjectsFindResponse; + }); + + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + expect(await api.fetchTableData()).toMatchInlineSnapshot(` + Array [ + Object { + "actions": Array [ + "reload", + "extend", + "cancel", + ], + "appId": "pizza", + "created": undefined, + "expires": undefined, + "id": "hello-pizza-123", + "name": "Veggie", + "reloadUrl": "hello-cool-undefined-url", + "restoreUrl": "hello-cool-undefined-url", + "status": "complete", + }, + ] + `); + }); + + test('handle error from sessionsClient response', async () => { + sessionsClient.find = jest.fn().mockRejectedValue(new Error('implementation is so bad')); + + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + await api.fetchTableData(); + + expect(mockCoreStart.notifications.toasts.addError).toHaveBeenCalledWith( + new Error('implementation is so bad'), + { title: 'Failed to refresh the page!' } + ); + }); + + test('handle timeout error', async () => { + mockConfig = { + ...mockConfig, + refreshInterval: moment.duration(1, 'hours'), + refreshTimeout: moment.duration(1, 'seconds'), + }; + + sessionsClient.find = jest.fn().mockImplementation(async () => { + return new Promise((resolve) => { + setTimeout(resolve, 2000); + }); + }); + + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + await api.fetchTableData(); + + expect(mockCoreStart.notifications.toasts.addDanger).toHaveBeenCalledWith( + 'Fetching the Search Session info timed out after 1 seconds' + ); + }); + }); + + describe('cancel', () => { + beforeEach(() => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return { + saved_objects: [ + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie', appId: 'pizza', status: 'baked' }, + }, + ], + } as SavedObjectsFindResponse; + }); + }); + + test('send cancel calls the cancel endpoint with a session ID', async () => { + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + await api.sendCancel('abc-123-cool-session-ID'); + + expect(mockCoreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith({ + title: 'The search session was canceled and expired.', + }); + }); + + test('error if deleting shows a toast message', async () => { + sessionsClient.delete = jest.fn().mockRejectedValue(new Error('implementation is so bad')); + + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + await api.sendCancel('abc-123-cool-session-ID'); + + expect(mockCoreStart.notifications.toasts.addError).toHaveBeenCalledWith( + new Error('implementation is so bad'), + { title: 'Failed to cancel the search session!' } + ); + }); + }); + + describe('reload', () => { + beforeEach(() => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return { + saved_objects: [ + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie', appId: 'pizza', status: SearchSessionStatus.COMPLETE }, + }, + ], + } as SavedObjectsFindResponse; + }); + }); + + test('send cancel calls the cancel endpoint with a session ID', async () => { + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + await api.reloadSearchSession('www.myurl.com'); + + expect(mockCoreStart.application.navigateToUrl).toHaveBeenCalledWith('www.myurl.com'); + }); + }); + + describe('extend', () => { + beforeEach(() => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return { + saved_objects: [ + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie', appId: 'pizza', status: SearchSessionStatus.COMPLETE }, + }, + ], + } as SavedObjectsFindResponse; + }); + }); + + test('send extend throws an error for now', async () => { + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + await api.sendExtend('my-id', '5d'); + + expect(mockCoreStart.notifications.toasts.addError).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts new file mode 100644 index 0000000000000..a2bd6b1a549be --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -0,0 +1,182 @@ +/* + * 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 type { ApplicationStart, NotificationsStart, SavedObject } from 'kibana/public'; +import moment from 'moment'; +import { from, race, timer } from 'rxjs'; +import { mapTo, tap } from 'rxjs/operators'; +import type { SharePluginStart } from 'src/plugins/share/public'; +import { SessionsMgmtConfigSchema } from '../'; +import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; +import type { SearchSessionSavedObjectAttributes } from '../../../../common'; +import { SearchSessionStatus } from '../../../../common/search'; +import { ACTION } from '../components/actions'; +import { UISession } from '../types'; + +type UrlGeneratorsStart = SharePluginStart['urlGenerators']; + +function getActions(status: SearchSessionStatus) { + const actions: ACTION[] = []; + actions.push(ACTION.RELOAD); + if (status === SearchSessionStatus.IN_PROGRESS || status === SearchSessionStatus.COMPLETE) { + actions.push(ACTION.EXTEND); + actions.push(ACTION.CANCEL); + } + return actions; +} + +async function getUrlFromState( + urls: UrlGeneratorsStart, + urlGeneratorId: string, + state: Record +) { + let url = '/'; + try { + url = await urls.getUrlGenerator(urlGeneratorId).createUrl(state); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Could not create URL from restoreState'); + // eslint-disable-next-line no-console + console.error(err); + } + return url; +} + +// Helper: factory for a function to map server objects to UI objects +const mapToUISession = ( + urls: UrlGeneratorsStart, + { expiresSoonWarning }: SessionsMgmtConfigSchema +) => async (savedObject: SavedObject): Promise => { + const { + name, + appId, + created, + expires, + status, + urlGeneratorId, + initialState, + restoreState, + } = savedObject.attributes; + + const actions = getActions(status); + + // TODO: initialState should be saved without the searchSessionID + if (initialState) delete initialState.searchSessionId; + // derive the URL and add it in + const reloadUrl = await getUrlFromState(urls, urlGeneratorId, initialState); + const restoreUrl = await getUrlFromState(urls, urlGeneratorId, restoreState); + + return { + id: savedObject.id, + name, + appId, + created, + expires, + status, + actions, + restoreUrl, + reloadUrl, + }; +}; + +interface SearcgSessuibManagementDeps { + urls: UrlGeneratorsStart; + notifications: NotificationsStart; + application: ApplicationStart; +} + +export class SearchSessionsMgmtAPI { + constructor( + private sessionsClient: ISessionsClient, + private config: SessionsMgmtConfigSchema, + private deps: SearcgSessuibManagementDeps + ) {} + + public async fetchTableData(): Promise { + interface FetchResult { + saved_objects: object[]; + } + + const refreshTimeout = moment.duration(this.config.refreshTimeout); + + const fetch$ = from( + this.sessionsClient.find({ + page: 1, + perPage: this.config.maxSessions, + sortField: 'created', + sortOrder: 'asc', + }) + ); + const timeout$ = timer(refreshTimeout.asMilliseconds()).pipe( + tap(() => { + this.deps.notifications.toasts.addDanger( + i18n.translate('xpack.data.mgmt.searchSessions.api.fetchTimeout', { + defaultMessage: 'Fetching the Search Session info timed out after {timeout} seconds', + values: { timeout: refreshTimeout.asSeconds() }, + }) + ); + }), + mapTo(null) + ); + + // fetch the search sessions before timeout triggers + try { + const result = await race(fetch$, timeout$).toPromise(); + if (result && result.saved_objects) { + const savedObjects = result.saved_objects as Array< + SavedObject + >; + return await Promise.all(savedObjects.map(mapToUISession(this.deps.urls, this.config))); + } + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + this.deps.notifications.toasts.addError(err, { + title: i18n.translate('xpack.data.mgmt.searchSessions.api.fetchError', { + defaultMessage: 'Failed to refresh the page!', + }), + }); + } + + return []; + } + + public reloadSearchSession(reloadUrl: string) { + this.deps.application.navigateToUrl(reloadUrl); + } + + // Cancel and expire + public async sendCancel(id: string): Promise { + try { + await this.sessionsClient.delete(id); + + this.deps.notifications.toasts.addSuccess({ + title: i18n.translate('xpack.data.mgmt.searchSessions.api.canceled', { + defaultMessage: 'The search session was canceled and expired.', + }), + }); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + + this.deps.notifications.toasts.addError(err, { + title: i18n.translate('xpack.data.mgmt.searchSessions.api.cancelError', { + defaultMessage: 'Failed to cancel the search session!', + }), + }); + } + } + + // Extend + public async sendExtend(id: string, ttl: string): Promise { + this.deps.notifications.toasts.addError(new Error('Not implemented'), { + title: i18n.translate('xpack.data.mgmt.searchSessions.api.extendError', { + defaultMessage: 'Failed to extend the session expiration!', + }), + }); + } +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts new file mode 100644 index 0000000000000..7640d8b80766e --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +import { DATE_STRING_FORMAT } from '../types'; + +export const dateString = (inputString: string, tz: string): string => { + if (inputString == null) { + throw new Error('Invalid date string!'); + } + let returnString: string; + if (tz === 'Browser') { + returnString = moment.utc(inputString).tz(moment.tz.guess()).format(DATE_STRING_FORMAT); + } else { + returnString = moment(inputString).tz(tz).format(DATE_STRING_FORMAT); + } + + return returnString; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts new file mode 100644 index 0000000000000..eac3245dfe2bc --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DocLinksStart } from 'kibana/public'; + +export class AsyncSearchIntroDocumentation { + private docsBasePath: string = ''; + + constructor(docs: DocLinksStart) { + const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docs; + const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`; + // TODO: There should be Kibana documentation link about Search Sessions in Kibana + this.docsBasePath = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`; + } + + public getElasticsearchDocLink() { + return `${this.docsBasePath}/async-search-intro.html`; + } +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx new file mode 100644 index 0000000000000..ce441efea7385 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -0,0 +1,208 @@ +/* + * 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 { EuiTableFieldDataColumnType } from '@elastic/eui'; +import { MockedKeys } from '@kbn/utility-types/jest'; +import { mount } from 'enzyme'; +import { CoreSetup, CoreStart } from 'kibana/public'; +import moment from 'moment'; +import { ReactElement } from 'react'; +import { coreMock } from 'src/core/public/mocks'; +import { SessionsClient } from 'src/plugins/data/public/search'; +import { SessionsMgmtConfigSchema } from '../'; +import { SearchSessionStatus } from '../../../../common/search'; +import { OnActionComplete } from '../components'; +import { UISession } from '../types'; +import { mockUrls } from '../__mocks__'; +import { SearchSessionsMgmtAPI } from './api'; +import { getColumns } from './get_columns'; + +let mockCoreSetup: MockedKeys; +let mockCoreStart: CoreStart; +let mockConfig: SessionsMgmtConfigSchema; +let api: SearchSessionsMgmtAPI; +let sessionsClient: SessionsClient; +let handleAction: OnActionComplete; +let mockSession: UISession; + +let tz = 'UTC'; + +describe('Search Sessions Management table column factory', () => { + beforeEach(async () => { + mockCoreSetup = coreMock.createSetup(); + mockCoreStart = coreMock.createStart(); + mockConfig = { + expiresSoonWarning: moment.duration(1, 'days'), + maxSessions: 2000, + refreshInterval: moment.duration(1, 'seconds'), + refreshTimeout: moment.duration(10, 'minutes'), + }; + sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); + + api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + tz = 'UTC'; + + handleAction = () => { + throw new Error('not testing handle action'); + }; + + mockSession = { + name: 'Cool mock session', + id: 'wtywp9u2802hahgp-thao', + reloadUrl: '/app/great-app-url', + restoreUrl: '/app/great-app-url/#42', + appId: 'discovery', + status: SearchSessionStatus.IN_PROGRESS, + created: '2020-12-02T00:19:32Z', + expires: '2020-12-07T00:19:32Z', + }; + }); + + test('returns columns', () => { + const columns = getColumns(mockCoreStart, api, mockConfig, tz, handleAction); + expect(columns).toMatchInlineSnapshot(` + Array [ + Object { + "field": "appId", + "name": "App", + "render": [Function], + "sortable": true, + }, + Object { + "field": "name", + "name": "Name", + "render": [Function], + "sortable": true, + "width": "20%", + }, + Object { + "field": "status", + "name": "Status", + "render": [Function], + "sortable": true, + }, + Object { + "field": "created", + "name": "Created", + "render": [Function], + "sortable": true, + }, + Object { + "field": "expires", + "name": "Expiration", + "render": [Function], + "sortable": true, + }, + Object { + "field": "status", + "name": "", + "render": [Function], + "sortable": false, + }, + Object { + "field": "actions", + "name": "", + "render": [Function], + "sortable": false, + }, + ] + `); + }); + + describe('name', () => { + test('rendering', () => { + const [, nameColumn] = getColumns(mockCoreStart, api, mockConfig, tz, handleAction) as Array< + EuiTableFieldDataColumnType + >; + + const name = mount(nameColumn.render!(mockSession.name, mockSession) as ReactElement); + + expect(name.text()).toBe('Cool mock session'); + }); + }); + + // Status column + describe('status', () => { + test('render in_progress', () => { + const [, , status] = getColumns(mockCoreStart, api, mockConfig, tz, handleAction) as Array< + EuiTableFieldDataColumnType + >; + + const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); + expect( + statusLine.find('.euiText[data-test-subj="sessionManagementStatusTooltip"]').text() + ).toMatchInlineSnapshot(`"In progress"`); + }); + + test('error handling', () => { + const [, , status] = getColumns(mockCoreStart, api, mockConfig, tz, handleAction) as Array< + EuiTableFieldDataColumnType + >; + + mockSession.status = 'INVALID' as SearchSessionStatus; + const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); + + // no unhandled error + + expect(statusLine.text()).toMatchInlineSnapshot(`"INVALID"`); + }); + }); + + // Start Date column + describe('startedDate', () => { + test('render using Browser timezone', () => { + tz = 'Browser'; + + const [, , , createdDateCol] = getColumns( + mockCoreStart, + api, + mockConfig, + tz, + handleAction + ) as Array>; + + const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); + + expect(date.text()).toBe('1 Dec, 2020, 19:19:32'); + }); + + test('render using AK timezone', () => { + tz = 'US/Alaska'; + + const [, , , createdDateCol] = getColumns( + mockCoreStart, + api, + mockConfig, + tz, + handleAction + ) as Array>; + + const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); + + expect(date.text()).toBe('1 Dec, 2020, 15:19:32'); + }); + + test('error handling', () => { + const [, , , createdDateCol] = getColumns( + mockCoreStart, + api, + mockConfig, + tz, + handleAction + ) as Array>; + + mockSession.created = 'INVALID'; + const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); + + // no unhandled error + expect(date.text()).toBe('Invalid date'); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx new file mode 100644 index 0000000000000..090336c37a98f --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -0,0 +1,233 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiBadge, + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiIconTip, + EuiLink, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { CoreStart } from 'kibana/public'; +import { capitalize } from 'lodash'; +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; +import { SessionsMgmtConfigSchema } from '../'; +import { SearchSessionStatus } from '../../../../common/search'; +import { TableText } from '../components'; +import { OnActionComplete, PopoverActionsMenu } from '../components'; +import { StatusIndicator } from '../components/status'; +import { dateString } from '../lib/date_string'; +import { SearchSessionsMgmtAPI } from './api'; +import { getExpirationStatus } from './get_expiration_status'; +import { UISession } from '../types'; + +// Helper function: translate an app string to EuiIcon-friendly string +const appToIcon = (app: string) => { + if (app === 'dashboards') { + return 'dashboard'; + } + return app; +}; + +function isSessionRestorable(status: SearchSessionStatus) { + return status === SearchSessionStatus.IN_PROGRESS || status === SearchSessionStatus.COMPLETE; +} + +export const getColumns = ( + core: CoreStart, + api: SearchSessionsMgmtAPI, + config: SessionsMgmtConfigSchema, + timezone: string, + onActionComplete: OnActionComplete +): Array> => { + // Use a literal array of table column definitions to detail a UISession object + return [ + // App + { + field: 'appId', + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerType', { + defaultMessage: 'App', + }), + sortable: true, + render: (appId: UISession['appId'], { id }) => { + const app = `${appToIcon(appId)}`; + return ( + + + + ); + }, + }, + + // Name, links to app and displays the search session data + { + field: 'name', + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerName', { + defaultMessage: 'Name', + }), + sortable: true, + width: '20%', + render: (name: UISession['name'], { restoreUrl, reloadUrl, status }) => { + const isRestorable = isSessionRestorable(status); + const notRestorableWarning = isRestorable ? null : ( + <> + {' '} + + } + /> + + ); + return ( + + + + {name} + {notRestorableWarning} + + + + ); + }, + }, + + // Session status + { + field: 'status', + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerStatus', { + defaultMessage: 'Status', + }), + sortable: true, + render: (statusType: UISession['status'], session) => ( + + ), + }, + + // Started date + { + field: 'created', + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerStarted', { + defaultMessage: 'Created', + }), + sortable: true, + render: (created: UISession['created'], { id }) => { + try { + const startedOn = dateString(created, timezone); + return ( + + {startedOn} + + ); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + return {created}; + } + }, + }, + + // Expiration date + { + field: 'expires', + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerExpiration', { + defaultMessage: 'Expiration', + }), + sortable: true, + render: (expires: UISession['expires'], { id, status }) => { + if ( + expires && + status !== SearchSessionStatus.EXPIRED && + status !== SearchSessionStatus.CANCELLED && + status !== SearchSessionStatus.ERROR + ) { + try { + const expiresOn = dateString(expires, timezone); + + // return + return ( + + {expiresOn} + + ); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + return {expires}; + } + } + return ( + + -- + + ); + }, + }, + + // Highlight Badge, if completed session expires soon + { + field: 'status', + name: '', + sortable: false, + render: (status, { expires }) => { + const expirationStatus = getExpirationStatus(config, expires); + if (expirationStatus) { + const { toolTipContent, statusContent } = expirationStatus; + + return ( + + + {statusContent} + + + ); + } + + return ; + }, + }, + + // Action(s) in-line in the row, additional action(s) in the popover, no column header + { + field: 'actions', + name: '', + sortable: false, + render: (actions: UISession['actions'], session) => { + if (actions && actions.length) { + return ( + + + + + + ); + } + }, + }, + ]; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts new file mode 100644 index 0000000000000..3c167d6dbe41a --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.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 { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import { SessionsMgmtConfigSchema } from '../'; + +export const getExpirationStatus = (config: SessionsMgmtConfigSchema, expires: string | null) => { + const tNow = moment.utc().valueOf(); + const tFuture = moment.utc(expires).valueOf(); + + // NOTE this could end up negative. If server time is off from the browser's clock + // and the session was early expired when the browser refreshed the listing + const durationToExpire = moment.duration(tFuture - tNow); + const expiresInDays = Math.floor(durationToExpire.asDays()); + const sufficientDays = Math.ceil(moment.duration(config.expiresSoonWarning).asDays()); + + let toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresSoonInDays', { + defaultMessage: 'Expires in {numDays} days', + values: { numDays: expiresInDays }, + }); + let statusContent = i18n.translate( + 'xpack.data.mgmt.searchSessions.status.expiresSoonInDaysTooltip', + { defaultMessage: '{numDays} days', values: { numDays: expiresInDays } } + ); + + if (expiresInDays === 0) { + // switch to show expires in hours + const expiresInHours = Math.floor(durationToExpire.asHours()); + + toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresSoonInHours', { + defaultMessage: 'This session expires in {numHours} hours', + values: { numHours: expiresInHours }, + }); + statusContent = i18n.translate( + 'xpack.data.mgmt.searchSessions.status.expiresSoonInHoursTooltip', + { defaultMessage: '{numHours} hours', values: { numHours: expiresInHours } } + ); + } + + if (durationToExpire.valueOf() > 0 && expiresInDays <= sufficientDays) { + return { toolTipContent, statusContent }; + } +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts new file mode 100644 index 0000000000000..78b91f7ca8ac2 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchSessionStatus } from '../../../common'; +import { ACTION } from './components/actions'; + +export const DATE_STRING_FORMAT = 'D MMM, YYYY, HH:mm:ss'; + +export interface UISession { + id: string; + name: string; + appId: string; + created: string; + expires: string | null; + status: SearchSessionStatus; + actions?: ACTION[]; + reloadUrl: string; + restoreUrl: string; +} diff --git a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx index ed022e18c34d7..361688581b4f1 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx @@ -66,7 +66,7 @@ const ContinueInBackgroundButton = ({ ); const ViewAllSearchSessionsButton = ({ - viewSearchSessionsLink = 'management', + viewSearchSessionsLink = 'management/kibana/search_sessions', buttonProps = {}, }: ActionButtonProps) => ( { let mockCoreSetup: MockedKeys>; let mockContext: jest.Mocked; + let mockLogger: Logger; beforeEach(() => { mockCoreSetup = coreMock.createSetup(); + mockLogger = coreMock.createPluginInitializerContext().logger.get(); mockContext = createSearchRequestHandlerContext(); - registerSessionRoutes(mockCoreSetup.http.createRouter()); + registerSessionRoutes(mockCoreSetup.http.createRouter(), mockLogger); }); it('save calls session.save with sessionId and attributes', async () => { diff --git a/x-pack/plugins/data_enhanced/server/routes/session.ts b/x-pack/plugins/data_enhanced/server/routes/session.ts index b056513f1d2f5..9e61dd39c83b8 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.ts @@ -5,10 +5,10 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import { IRouter, Logger } from 'src/core/server'; import { reportServerError } from '../../../../../src/plugins/kibana_utils/server'; -export function registerSessionRoutes(router: IRouter): void { +export function registerSessionRoutes(router: IRouter, logger: Logger): void { router.post( { path: '/internal/session', @@ -49,6 +49,7 @@ export function registerSessionRoutes(router: IRouter): void { body: response, }); } catch (err) { + logger.error(err); return reportServerError(res, err); } } @@ -73,6 +74,7 @@ export function registerSessionRoutes(router: IRouter): void { }); } catch (e) { const err = e.output?.payload || e; + logger.error(err); return reportServerError(res, err); } } @@ -106,6 +108,7 @@ export function registerSessionRoutes(router: IRouter): void { body: response, }); } catch (err) { + logger.error(err); return reportServerError(res, err); } } @@ -128,6 +131,7 @@ export function registerSessionRoutes(router: IRouter): void { return res.ok(); } catch (e) { const err = e.output?.payload || e; + logger.error(err); return reportServerError(res, err); } } @@ -156,6 +160,7 @@ export function registerSessionRoutes(router: IRouter): void { body: response, }); } catch (err) { + logger.error(err); return reportServerError(res, err); } } diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts index f37aaf71fded5..1107ed8155080 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts @@ -114,6 +114,7 @@ describe('SearchSessionService', () => { maxUpdateRetries: 3, defaultExpiration: moment.duration(7, 'd'), trackingInterval: moment.duration(10, 's'), + management: {} as any, }, }, }); diff --git a/x-pack/plugins/data_enhanced/tsconfig.json b/x-pack/plugins/data_enhanced/tsconfig.json index ec5c656ac50b5..c4b09276880d9 100644 --- a/x-pack/plugins/data_enhanced/tsconfig.json +++ b/x-pack/plugins/data_enhanced/tsconfig.json @@ -12,6 +12,7 @@ "public/**/*", "server/**/*", "config.ts", + "../../../typings/**/*", // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 "public/autocomplete/providers/kql_query_suggestion/__fixtures__/*.json" ], @@ -22,6 +23,7 @@ { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../../../src/plugins/management/tsconfig.json" }, { "path": "../task_manager/tsconfig.json" }, { "path": "../features/tsconfig.json" }, diff --git a/x-pack/test/functional/es_archives/data/search_sessions/data.json.gz b/x-pack/test/functional/es_archives/data/search_sessions/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..28260ee99e4dc0c428d5e2a75a0a14db990e2723 GIT binary patch literal 1956 zcmZ9Jc|6m79KeSh9TP&B^*E=Nk)y(zq+{8LFt-&g=T?br)XoBaqK zZlb5cW0S}AEXNoT5lf!+Jg?`U=dbr4-`D5+{(Rrx-{^93mpp!)0Z9@3BK(X(!Y^I` z&E|N!3|c71j)?ZMhHD8@t*9J_!wEVeYp7D*Bil)Yt)84uT*GW^y?+%tW0T~j_N zzst5vQ8k>o9AWg>JlB#6&98gttUIN4dY6VVvJGq(`ZJnql9pJL>7vx5_0)vfo3wJ0 zm3VFZV)qQe-qf_Ifzv&slMBw(%amrO9g0HJ)7DeU{dKH=H$&}Ee|VATdgPqf2g3sW zs0HTY>NpDmF@Xo!(IIcgOOmo#HGYpKq#u8Fr?oBcCa~!0ZFGWH=l$d{oHKOA$)?of#1DMyBX+L6o41yzk9C_(?owkc!ja#8) zSFKe`6{9S(9R&&Uz##~kG(F&(EU3kn@X31slr3MbD(^Bs1Y+cNAhKv9R&g@w(YKK& z2bZg=1d+@6OR%NBhiRoxkE;?n?DFb*)91vb9oZ+MaJYG>26g1S!bjMn!!Z-@ai)2) z4YOA!(61N4T?cV+FSwW6`IVYm$_=k9dKZs2nsPr*SH7DDh`r=8G&9G(I9O@RZ8X`= zNU&SSE)wE1zC*)sC+}4Iial`Us$Rxt7lMV$83j(koLub`S*?4`GFSqf-x=t>NSmv?hGnI{*?t4V;vxS0q#YCy8rrK1v z&w*%Wa9G!_FBz}#YCC13($j>jfLA(R731|6%TTAB0c8K0z${7$X`OGTTb9o%Jj4^Y6p*AwLY%5 zwi*A*uW&zL&(q9|uw`Q$)VS3OEa7~$(GT+Z+{q0LALi+-uLpJgfdR%eQ?KA7Z+!U9 zJe11VMOK5UL&9|%2DQld7Y~G;IH@=>dDFu9csT#13E%3lj9%mTr@`SZ0h93&l9tNo z7O*aaYS)rrh5sC}UO7p9Y4LR}k4`dp+O`tdNqb~+Brt?dkJdp+hS%3#d}r=Br=ycfv^N6<3a8F1s>9t#`9 z)~~l{=V9g8s}hSB-@MLWXTKcl z95}&vo*K3o(_ZBuL?ZnMdtyn_cX2#GVo+h;F@Dy4m5}W`kp-IVAGm}Lx@FdTc!&R> zMq;^5RT6LJdWod}(drFz|1Vo_N#SeW$05IZrNJU$wZmT>oseT*jeaT>zHjz#5~cfA zZr)mFE`7G(l|#M%t~e(JXpeq750&^ts?sJvb6Vy?wE)RhxWCAT zm`L&UM30ia4dp!BWh!F8wQiR@ZiFU-xC+8G76T*%cnU0r1A&j|dPY40B(=f`d#8z@ z0fo84vI9}ji6=GL8`wo5t66`l42Bx34x)=}dcwn;gLZRVp%SI(k{E<`9|wjafhgEA zIlK4!iJ{_?1&?^rZVoM$`(SBPP)%z>$`Vn@`zW(;i+HD_O#Nf^K!L%09YU2>5Gcjf zz#wp%ZewC-^Y+6~5P?seM(KfE=>K=I>hqLGQQ*=zXrrTehu_- z0-(@;)Sm7CSCju$lmD+S)BQ(XcJ4p*=%+YmtQ_TzCmKZYCW<@3SO{=o23+-7iWmxW z*(^rv48YsYVr+E{Eg`#_c*KX4kZDfbY;uIee}7S)*MGY8oC=WFuPBK>*RS|wNEKq+ zUu8!Ga~N>tXNh7c%zk1q-pl|V;^JVRUpIw$#EU^Y%Y8uJboN&#vd7gFt@+=FP|z literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json new file mode 100644 index 0000000000000..24bbcbea23385 --- /dev/null +++ b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json @@ -0,0 +1,2596 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "49eb3350984bd2a162914d3776e70cfb", + "api_key_pending_invalidation": "16f515278a295f6245149ad7c5ddedb7", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "background-session": "dfd06597e582fdbbbc09f1a3615e6ce0", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "477f214ff61acc3af26a7b7818e380c1", + "cases-comments": "8a50736330e953bca91747723a319593", + "cases-configure": "387c5f3a3bda7e0ae0dd4e106f914a69", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "core-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", + "dashboard": "40554caf09725935e2c02e02563a2d07", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "enterprise_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "epm-packages": "0cbbb16506734d341a96aaed65ec6413", + "epm-packages-assets": "44621b2f6052ef966da47b7c3a00f33b", + "exception-list": "67f055ab8c10abd7b2ebfd969b836788", + "exception-list-agnostic": "67f055ab8c10abd7b2ebfd969b836788", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "9511b565b1cc6441a42033db3d5de8e9", + "fleet-agent-events": "e20a508b6e805189356be381dbfac8db", + "fleet-agents": "cb661e8ede2b640c42c8e5ef99db0683", + "fleet-enrollment-api-keys": "a69ef7ae661dab31561d6c6f052ef2a7", + "graph-workspace": "27a94b2edcb0610c6aea54a7c56d7752", + "index-pattern": "45915a1ad866812242df474eb0479052", + "infrastructure-ui-source": "3d1b76c39bfb2cc8296b024d73854724", + "ingest-agent-policies": "8b0733cce189659593659dad8db426f0", + "ingest-outputs": "8854f34453a47e26f86a29f8f3b80b4e", + "ingest-package-policies": "c91ca97b1ff700f0fc64dc6b13d65a85", + "ingest_manager_settings": "02a03095f0e05b7a538fa801b88a217f", + "inventory-view": "3d1b76c39bfb2cc8296b024d73854724", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "52346cfec69ff7b47d5f0c12361a2797", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "3d1b76c39bfb2cc8296b024d73854724", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-job": "3bb64c31915acf93fc724af137a0891b", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "monitoring-telemetry": "2669d5ec15e82391cf58df4294ee9c68", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "originId": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "43012c7ebc4cb57054e0a490e4b43023", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "d12c5474364d737d17252acf1dc4585c", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "spaces-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", + "tag": "83d55da58f6530f7055415717ec06474", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "3d1b76c39bfb2cc8296b024d73854724", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "f819cf6636b75c9e76ba733a0c6ef355", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "executionStatus": { + "properties": { + "error": { + "properties": { + "message": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + } + } + }, + "lastExecutionDate": { + "type": "date" + }, + "status": { + "type": "keyword" + } + } + }, + "meta": { + "properties": { + "versionApiKeyLastmodified": { + "type": "keyword" + } + } + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "notifyWhen": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedAt": { + "type": "date" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "api_key_pending_invalidation": { + "properties": { + "apiKeyId": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "search-session": { + "properties": { + "appId": { + "type": "keyword" + }, + "created": { + "type": "date" + }, + "expires": { + "type": "date" + }, + "idMapping": { + "enabled": false, + "type": "object" + }, + "initialState": { + "enabled": false, + "type": "object" + }, + "name": { + "type": "keyword" + }, + "restoreState": { + "enabled": false, + "type": "object" + }, + "sessionId": { + "type": "keyword" + }, + "status": { + "type": "keyword" + }, + "urlGeneratorId": { + "type": "keyword" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector": { + "properties": { + "fields": { + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "alertId": { + "type": "keyword" + }, + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "index": { + "type": "keyword" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector": { + "properties": { + "fields": { + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "core-usage-stats": { + "dynamic": "false", + "type": "object" + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "optionsJSON": { + "index": false, + "type": "text" + }, + "panelsJSON": { + "index": false, + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "pause": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "section": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "value": { + "doc_values": false, + "index": false, + "type": "integer" + } + } + }, + "timeFrom": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "timeRestore": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "timeTo": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "enterprise_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "install_source": { + "type": "keyword" + }, + "install_started_at": { + "type": "date" + }, + "install_status": { + "type": "keyword" + }, + "install_version": { + "type": "keyword" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "package_assets": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "epm-packages-assets": { + "properties": { + "asset_path": { + "type": "keyword" + }, + "data_base64": { + "type": "binary" + }, + "data_utf8": { + "index": false, + "type": "text" + }, + "install_source": { + "type": "keyword" + }, + "media_type": { + "type": "keyword" + }, + "package_name": { + "type": "keyword" + }, + "package_version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "ack_data": { + "type": "text" + }, + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "policy_id": { + "type": "keyword" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "upgrade_started_at": { + "type": "date" + }, + "upgraded_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "legacyIndexPatternRef": { + "index": false, + "type": "text" + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "dynamic": "false", + "properties": { + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "dynamic": "false", + "type": "object" + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_policies": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "config_yaml": { + "type": "text" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "compiled_input": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "policy_id": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_urls": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "dynamic": "false", + "type": "object" + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "dynamic": "false", + "type": "object" + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-job": { + "properties": { + "datafeed_id": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "job_id": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "monitoring-telemetry": { + "properties": { + "reportedClusterUuids": { + "type": "keyword" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "originId": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "indexNames": { + "type": "text" + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "spaces-usage-stats": { + "dynamic": "false", + "type": "object" + }, + "tag": { + "properties": { + "color": { + "type": "text" + }, + "description": { + "type": "text" + }, + "name": { + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-counter": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "dynamic": "false", + "type": "object" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "savedSearchRefName": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "index": false, + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "index": false, + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts index 4c523ec5706e1..20b8acb9d4509 100644 --- a/x-pack/test/functional/page_objects/index.ts +++ b/x-pack/test/functional/page_objects/index.ts @@ -38,6 +38,7 @@ import { SpaceSelectorPageProvider } from './space_selector_page'; import { IngestPipelinesPageProvider } from './ingest_pipelines_page'; import { TagManagementPageProvider } from './tag_management_page'; import { NavigationalSearchProvider } from './navigational_search'; +import { SearchSessionsPageProvider } from './search_sessions_management_page'; // just like services, PageObjects are defined as a map of // names to Providers. Merge in Kibana's or pick specific ones @@ -64,6 +65,7 @@ export const pageObjects = { apiKeys: ApiKeysPageProvider, licenseManagement: LicenseManagementPageProvider, indexManagement: IndexManagementPageProvider, + searchSessionsManagement: SearchSessionsPageProvider, indexLifecycleManagement: IndexLifecycleManagementPageProvider, tagManagement: TagManagementPageProvider, snapshotRestore: SnapshotRestorePageProvider, diff --git a/x-pack/test/functional/page_objects/search_sessions_management_page.ts b/x-pack/test/functional/page_objects/search_sessions_management_page.ts new file mode 100644 index 0000000000000..99c3be82a214d --- /dev/null +++ b/x-pack/test/functional/page_objects/search_sessions_management_page.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function SearchSessionsPageProvider({ getService, getPageObjects }: FtrProviderContext) { + const find = getService('find'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common']); + + return { + async goTo() { + await PageObjects.common.navigateToApp('management/kibana/search_sessions'); + }, + + async refresh() { + await testSubjects.click('sessionManagementRefreshBtn'); + }, + + async getList() { + const table = await find.byCssSelector('table'); + const allRows = await table.findAllByTestSubject('searchSessionsRow'); + + return Promise.all( + allRows.map(async (row) => { + const $ = await row.parseDomContent(); + const viewCell = await row.findByTestSubject('sessionManagementNameCol'); + const actionsCell = await row.findByTestSubject('sessionManagementActionsCol'); + return { + name: $.findTestSubject('sessionManagementNameCol').text(), + status: $.findTestSubject('sessionManagementStatusLabel').attr('data-test-status'), + mainUrl: $.findTestSubject('sessionManagementNameCol').text(), + created: $.findTestSubject('sessionManagementCreatedCol').text(), + expires: $.findTestSubject('sessionManagementExpiresCol').text(), + app: $.findTestSubject('sessionManagementAppIcon').attr('data-test-app-id'), + view: async () => { + await viewCell.click(); + }, + reload: async () => { + await actionsCell.click(); + await find.clickByCssSelector( + '[data-test-subj="sessionManagementPopoverAction-reload"]' + ); + }, + cancel: async () => { + await actionsCell.click(); + await find.clickByCssSelector( + '[data-test-subj="sessionManagementPopoverAction-cancel"]' + ); + await PageObjects.common.clickConfirmOnModal(); + }, + }; + }) + ); + }, + }; +} diff --git a/x-pack/test/send_search_to_background_integration/config.ts b/x-pack/test/send_search_to_background_integration/config.ts index c14678febd811..bad818bb69664 100644 --- a/x-pack/test/send_search_to_background_integration/config.ts +++ b/x-pack/test/send_search_to_background_integration/config.ts @@ -23,6 +23,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [ resolve(__dirname, './tests/apps/dashboard/async_search'), resolve(__dirname, './tests/apps/discover'), + resolve(__dirname, './tests/apps/management/search_sessions'), ], kbnTestServer: { diff --git a/x-pack/test/send_search_to_background_integration/services/index.ts b/x-pack/test/send_search_to_background_integration/services/index.ts index 91b0ad502d053..35eed5a218b42 100644 --- a/x-pack/test/send_search_to_background_integration/services/index.ts +++ b/x-pack/test/send_search_to_background_integration/services/index.ts @@ -9,5 +9,5 @@ import { SendToBackgroundProvider } from './send_to_background'; export const services = { ...functionalServices, - sendToBackground: SendToBackgroundProvider, + searchSessions: SendToBackgroundProvider, }; diff --git a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts index 319496239de34..8c3261c2074ae 100644 --- a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { SavedObjectsFindResponse } from 'src/core/server'; import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; +import { FtrProviderContext } from '../ftr_provider_context'; -const SEND_TO_BACKGROUND_TEST_SUBJ = 'searchSessionIndicator'; -const SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ = 'searchSessionIndicatorPopoverContainer'; +const SEARCH_SESSION_INDICATOR_TEST_SUBJ = 'searchSessionIndicator'; +const SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ = 'searchSessionIndicatorPopoverContainer'; type SessionStateType = | 'none' @@ -21,22 +22,24 @@ type SessionStateType = export function SendToBackgroundProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const log = getService('log'); const retry = getService('retry'); const browser = getService('browser'); + const supertest = getService('supertest'); return new (class SendToBackgroundService { public async find(): Promise { - return testSubjects.find(SEND_TO_BACKGROUND_TEST_SUBJ); + return testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ); } public async exists(): Promise { - return testSubjects.exists(SEND_TO_BACKGROUND_TEST_SUBJ); + return testSubjects.exists(SEARCH_SESSION_INDICATOR_TEST_SUBJ); } public async expectState(state: SessionStateType) { - return retry.waitFor(`sendToBackground indicator to get into state = ${state}`, async () => { + return retry.waitFor(`searchSessions indicator to get into state = ${state}`, async () => { const currentState = await ( - await testSubjects.find(SEND_TO_BACKGROUND_TEST_SUBJ) + await testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ) ).getAttribute('data-state'); return currentState === state; }); @@ -65,23 +68,57 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { await this.ensurePopoverClosed(); } + public async openPopover() { + await this.ensurePopoverOpened(); + } + private async ensurePopoverOpened() { - const isAlreadyOpen = await testSubjects.exists(SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ); + const isAlreadyOpen = await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); if (isAlreadyOpen) return; - return retry.waitFor(`sendToBackground popover opened`, async () => { - await testSubjects.click(SEND_TO_BACKGROUND_TEST_SUBJ); - return await testSubjects.exists(SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ); + return retry.waitFor(`searchSessions popover opened`, async () => { + await testSubjects.click(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + return await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); }); } private async ensurePopoverClosed() { const isAlreadyClosed = !(await testSubjects.exists( - SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ + SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ )); if (isAlreadyClosed) return; - return retry.waitFor(`sendToBackground popover closed`, async () => { + return retry.waitFor(`searchSessions popover closed`, async () => { await browser.pressKeys(browser.keys.ESCAPE); - return !(await testSubjects.exists(SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ)); + return !(await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ)); + }); + } + + /* + * This cleanup function should be used by tests that create new background sesions. + * Tests should not end with new background sessions remaining in storage since that interferes with functional tests that check the _find API. + * Alternatively, a test can navigate to `Managment > Search Sessions` and use the UI to delete any created tests. + */ + public async deleteAllSearchSessions() { + log.debug('Deleting created background sessions'); + // ignores 409 errs and keeps retrying + await retry.tryForTime(10000, async () => { + const { body } = await supertest + .post('/internal/session/_find') + .set('kbn-xsrf', 'anything') + .set('kbn-system-request', 'true') + .send({ page: 1, perPage: 10000, sortField: 'created', sortOrder: 'asc' }) + .expect(200); + + const { saved_objects: savedObjects } = body as SavedObjectsFindResponse; + log.debug(`Found created background sessions: ${savedObjects.map(({ id }) => id)}`); + await Promise.all( + savedObjects.map(async (so) => { + log.debug(`Deleting background session: ${so.id}`); + await supertest + .delete(`/internal/session/${so.id}`) + .set(`kbn-xsrf`, `anything`) + .expect(200); + }) + ); }); } })(); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts index 2edaeb1918b25..03635efb6113d 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts @@ -14,7 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'dashboard', 'visChart']); const dashboardPanelActions = getService('dashboardPanelActions'); const browser = getService('browser'); - const sendToBackground = getService('sendToBackground'); + const searchSessions = getService('searchSessions'); describe('send to background', () => { before(async function () { @@ -26,6 +26,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('dashboard'); }); + after(async function () { + await searchSessions.deleteAllSearchSessions(); + }); + it('Restore using non-existing sessionId errors out. Refresh starts a new session and completes.', async () => { await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); const url = await browser.getCurrentUrl(); @@ -33,7 +37,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const savedSessionURL = `${url}&searchSessionId=${fakeSessionId}`; await browser.get(savedSessionURL); await PageObjects.header.waitUntilLoadingHasFinished(); - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); await testSubjects.existOrFail('embeddableErrorLabel'); // expected that panel errors out because of non existing session const session1 = await dashboardPanelActions.getSearchSessionIdByTitle( @@ -41,9 +45,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); expect(session1).to.be(fakeSessionId); - await sendToBackground.refresh(); + await searchSessions.refresh(); await PageObjects.header.waitUntilLoadingHasFinished(); - await sendToBackground.expectState('completed'); + await searchSessions.expectState('completed'); await testSubjects.missingOrFail('embeddableErrorLabel'); const session2 = await dashboardPanelActions.getSearchSessionIdByTitle( 'Sum of Bytes by Extension' @@ -54,9 +58,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Saves and restores a session', async () => { await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); await PageObjects.dashboard.waitForRenderComplete(); - await sendToBackground.expectState('completed'); - await sendToBackground.save(); - await sendToBackground.expectState('backgroundCompleted'); + await searchSessions.expectState('completed'); + await searchSessions.save(); + await searchSessions.expectState('backgroundCompleted'); const savedSessionId = await dashboardPanelActions.getSearchSessionIdByTitle( 'Sum of Bytes by Extension' ); @@ -69,7 +73,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); // Check that session is restored - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); await testSubjects.missingOrFail('embeddableErrorLabel'); const data = await PageObjects.visChart.getBarChartData('Sum of bytes'); expect(data.length).to.be(5); @@ -77,7 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // switching dashboard to edit mode (or any other non-fetch required) state change // should leave session state untouched await PageObjects.dashboard.switchToEditMode(); - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); }); }); } diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts index 9eb42b74668c8..ce6c8978c7d67 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const find = getService('find'); const dashboardExpect = getService('dashboardExpect'); const browser = getService('browser'); - const sendToBackground = getService('sendToBackground'); + const searchSessions = getService('searchSessions'); describe('send to background with relative time', () => { before(async () => { @@ -60,9 +60,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); await checkSampleDashboardLoaded(); - await sendToBackground.expectState('completed'); - await sendToBackground.save(); - await sendToBackground.expectState('backgroundCompleted'); + await searchSessions.expectState('completed'); + await searchSessions.save(); + await searchSessions.expectState('backgroundCompleted'); const savedSessionId = await dashboardPanelActions.getSearchSessionIdByTitle( '[Flights] Airline Carrier' ); @@ -80,7 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await checkSampleDashboardLoaded(); // Check that session is restored - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); }); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts index 7d00761b2fa9f..f590e44138642 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts @@ -20,7 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]); const dashboardPanelActions = getService('dashboardPanelActions'); const browser = getService('browser'); - const sendToBackground = getService('sendToBackground'); + const searchSessions = getService('searchSessions'); describe('dashboard in space', () => { describe('Send to background in space', () => { @@ -73,9 +73,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); - await sendToBackground.expectState('completed'); - await sendToBackground.save(); - await sendToBackground.expectState('backgroundCompleted'); + await searchSessions.expectState('completed'); + await searchSessions.save(); + await searchSessions.expectState('backgroundCompleted'); const savedSessionId = await dashboardPanelActions.getSearchSessionIdByTitle( 'A Pie in another space' ); @@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); // Check that session is restored - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); await testSubjects.missingOrFail('embeddableErrorLabel'); }); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts b/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts index 5c94a50e0a84d..6384afb179593 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts @@ -20,7 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'timePicker', ]); const browser = getService('browser'); - const sendToBackground = getService('sendToBackground'); + const searchSessions = getService('searchSessions'); describe('discover in space', () => { describe('Send to background in space', () => { @@ -74,9 +74,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitForDocTableLoadingComplete(); - await sendToBackground.expectState('completed'); - await sendToBackground.save(); - await sendToBackground.expectState('backgroundCompleted'); + await searchSessions.expectState('completed'); + await searchSessions.save(); + await searchSessions.expectState('backgroundCompleted'); await inspector.open(); const savedSessionId = await ( @@ -92,7 +92,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitForDocTableLoadingComplete(); // Check that session is restored - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); await testSubjects.missingOrFail('embeddableErrorLabel'); }); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/index.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/index.ts new file mode 100644 index 0000000000000..6a11a15f31567 --- /dev/null +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + + describe('search sessions management', function () { + this.tags('ciGroup3'); + + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('dashboard/async_search'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + await kibanaServer.uiSettings.replace({ 'search:timeout': 10000 }); + }); + + loadTestFile(require.resolve('./sessions_management')); + }); +} diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts new file mode 100644 index 0000000000000..f06e8eba0bf68 --- /dev/null +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts @@ -0,0 +1,148 @@ +/* + * 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects([ + 'common', + 'header', + 'dashboard', + 'visChart', + 'searchSessionsManagement', + ]); + const searchSessions = getService('searchSessions'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + + describe('Search search sessions Management UI', () => { + describe('New search sessions', () => { + before(async () => { + await PageObjects.common.navigateToApp('dashboard'); + }); + + after(async () => { + await searchSessions.deleteAllSearchSessions(); + }); + + it('Saves a session and verifies it in the Management app', async () => { + await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); + await PageObjects.dashboard.waitForRenderComplete(); + await searchSessions.expectState('completed'); + await searchSessions.save(); + await searchSessions.expectState('backgroundCompleted'); + + await searchSessions.openPopover(); + await searchSessions.viewSearchSessions(); + + await retry.waitFor(`wait for first item to complete`, async function () { + const s = await PageObjects.searchSessionsManagement.getList(); + return s[0] && s[0].status === 'complete'; + }); + + // find there is only one item in the table which is the newly saved session + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + expect(searchSessionList.length).to.be(1); + expect(searchSessionList[0].expires).not.to.eql('--'); + expect(searchSessionList[0].name).to.eql('Not Delayed'); + + // navigate to dashboard + await searchSessionList[0].view(); + + // embeddable has loaded + await testSubjects.existOrFail('embeddablePanelHeading-SumofBytesbyExtension'); + await PageObjects.dashboard.waitForRenderComplete(); + + // search session was restored + await searchSessions.expectState('restored'); + }); + + it('Reloads as new session from management', async () => { + await PageObjects.searchSessionsManagement.goTo(); + + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + + expect(searchSessionList.length).to.be(1); + await searchSessionList[0].reload(); + + // embeddable has loaded + await PageObjects.dashboard.waitForRenderComplete(); + + // new search session was completed + await searchSessions.expectState('completed'); + }); + + it('Cancels a session from management', async () => { + await PageObjects.searchSessionsManagement.goTo(); + + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + + expect(searchSessionList.length).to.be(1); + await searchSessionList[0].cancel(); + + // TODO: update this once canceling doesn't delete the object! + await retry.waitFor(`wait for list to be empty`, async function () { + const s = await PageObjects.searchSessionsManagement.getList(); + + return s.length === 0; + }); + }); + }); + + describe('Archived search sessions', () => { + before(async () => { + await PageObjects.searchSessionsManagement.goTo(); + }); + + after(async () => { + await searchSessions.deleteAllSearchSessions(); + }); + + it('shows no items found', async () => { + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + expect(searchSessionList.length).to.be(0); + }); + + it('autorefreshes and shows items on the server', async () => { + await esArchiver.load('data/search_sessions'); + + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + + expect(searchSessionList.length).to.be(10); + + expect(searchSessionList.map((ss) => ss.created)).to.eql([ + '25 Dec, 2020, 00:00:00', + '24 Dec, 2020, 00:00:00', + '23 Dec, 2020, 00:00:00', + '22 Dec, 2020, 00:00:00', + '21 Dec, 2020, 00:00:00', + '20 Dec, 2020, 00:00:00', + '19 Dec, 2020, 00:00:00', + '18 Dec, 2020, 00:00:00', + '17 Dec, 2020, 00:00:00', + '16 Dec, 2020, 00:00:00', + ]); + + expect(searchSessionList.map((ss) => ss.expires)).to.eql([ + '--', + '--', + '--', + '23 Dec, 2020, 00:00:00', + '22 Dec, 2020, 00:00:00', + '--', + '--', + '--', + '18 Dec, 2020, 00:00:00', + '17 Dec, 2020, 00:00:00', + ]); + + await esArchiver.unload('data/search_sessions'); + }); + }); + }); +} From 86789dabb567c478ffa5b418d52566a3363085b9 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Wed, 20 Jan 2021 17:49:21 +0100 Subject: [PATCH 13/83] [Lens] Add more in-editor Advanced documentation (#86821) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Wylie Conlon Co-authored-by: Michael Marcialis --- ...ibana-plugin-plugins-data-public.search.md | 11 + .../lib/time_buckets/calc_auto_interval.ts | 194 +++++++++++++++--- src/plugins/data/public/index.ts | 2 + src/plugins/data/public/public.api.md | 41 ++-- .../dimension_panel/dimension_editor.tsx | 31 ++- .../dimension_panel/dimension_panel.test.tsx | 3 + .../dimension_panel/reference_editor.tsx | 11 +- .../indexpattern_datasource/help_popover.scss | 13 ++ .../indexpattern_datasource/help_popover.tsx | 70 +++++++ .../calculations/moving_average.tsx | 85 +++++++- .../operations/definitions/date_histogram.tsx | 92 ++++++++- .../operations/definitions/index.ts | 8 + .../definitions/ranges/range_editor.tsx | 82 ++++++-- .../xy_visualization/xy_config_panel.scss | 2 +- .../xy_visualization/xy_config_panel.tsx | 23 ++- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 17 files changed, 578 insertions(+), 92 deletions(-) create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/help_popover.scss create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md index 22dc92c275670..4b3c915b49c2d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md @@ -35,6 +35,17 @@ search: { siblingPipelineType: string; termsAggFilter: string[]; toAbsoluteDates: typeof toAbsoluteDates; + boundsDescendingRaw: ({ + bound: number; + interval: import("moment").Duration; + boundLabel: string; + intervalLabel: string; + } | { + bound: import("moment").Duration; + interval: import("moment").Duration; + boundLabel: string; + intervalLabel: string; + })[]; }; getRequestInspectorStats: typeof getRequestInspectorStats; getResponseInspectorStats: typeof getResponseInspectorStats; diff --git a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts index 83fd22a618fec..3c1a89015252e 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts @@ -6,75 +6,205 @@ * Public License, v 1. */ +import { i18n } from '@kbn/i18n'; import moment from 'moment'; -const boundsDescending = [ +export const boundsDescendingRaw = [ { bound: Infinity, - interval: Number(moment.duration(1, 'year')), + interval: moment.duration(1, 'year'), + boundLabel: i18n.translate('data.search.timeBuckets.infinityLabel', { + defaultMessage: 'More than a year', + }), + intervalLabel: i18n.translate('data.search.timeBuckets.yearLabel', { + defaultMessage: 'a year', + }), }, { - bound: Number(moment.duration(1, 'year')), - interval: Number(moment.duration(1, 'month')), + bound: moment.duration(1, 'year'), + interval: moment.duration(1, 'month'), + boundLabel: i18n.translate('data.search.timeBuckets.yearLabel', { + defaultMessage: 'a year', + }), + intervalLabel: i18n.translate('data.search.timeBuckets.monthLabel', { + defaultMessage: 'a month', + }), }, { - bound: Number(moment.duration(3, 'week')), - interval: Number(moment.duration(1, 'week')), + bound: moment.duration(3, 'week'), + interval: moment.duration(1, 'week'), + boundLabel: i18n.translate('data.search.timeBuckets.dayLabel', { + defaultMessage: '{amount, plural, one {a day} other {# days}}', + values: { amount: 21 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.dayLabel', { + defaultMessage: '{amount, plural, one {a day} other {# days}}', + values: { amount: 7 }, + }), }, { - bound: Number(moment.duration(1, 'week')), - interval: Number(moment.duration(1, 'd')), + bound: moment.duration(1, 'week'), + interval: moment.duration(1, 'd'), + boundLabel: i18n.translate('data.search.timeBuckets.dayLabel', { + defaultMessage: '{amount, plural, one {a day} other {# days}}', + values: { amount: 7 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.dayLabel', { + defaultMessage: '{amount, plural, one {a day} other {# days}}', + values: { amount: 1 }, + }), }, { - bound: Number(moment.duration(24, 'hour')), - interval: Number(moment.duration(12, 'hour')), + bound: moment.duration(24, 'hour'), + interval: moment.duration(12, 'hour'), + boundLabel: i18n.translate('data.search.timeBuckets.dayLabel', { + defaultMessage: '{amount, plural, one {a day} other {# days}}', + values: { amount: 1 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.hourLabel', { + defaultMessage: '{amount, plural, one {an hour} other {# hours}}', + values: { amount: 12 }, + }), }, { - bound: Number(moment.duration(6, 'hour')), - interval: Number(moment.duration(3, 'hour')), + bound: moment.duration(6, 'hour'), + interval: moment.duration(3, 'hour'), + boundLabel: i18n.translate('data.search.timeBuckets.hourLabel', { + defaultMessage: '{amount, plural, one {an hour} other {# hours}}', + values: { amount: 6 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.hourLabel', { + defaultMessage: '{amount, plural, one {an hour} other {# hours}}', + values: { amount: 3 }, + }), }, { - bound: Number(moment.duration(2, 'hour')), - interval: Number(moment.duration(1, 'hour')), + bound: moment.duration(2, 'hour'), + interval: moment.duration(1, 'hour'), + boundLabel: i18n.translate('data.search.timeBuckets.hourLabel', { + defaultMessage: '{amount, plural, one {an hour} other {# hours}}', + values: { amount: 2 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.hourLabel', { + defaultMessage: '{amount, plural, one {an hour} other {# hours}}', + values: { amount: 1 }, + }), }, { - bound: Number(moment.duration(45, 'minute')), - interval: Number(moment.duration(30, 'minute')), + bound: moment.duration(45, 'minute'), + interval: moment.duration(30, 'minute'), + boundLabel: i18n.translate('data.search.timeBuckets.minuteLabel', { + defaultMessage: '{amount, plural, one {a minute} other {# minutes}}', + values: { amount: 45 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.minuteLabel', { + defaultMessage: '{amount, plural, one {a minute} other {# minutes}}', + values: { amount: 30 }, + }), }, { - bound: Number(moment.duration(20, 'minute')), - interval: Number(moment.duration(10, 'minute')), + bound: moment.duration(20, 'minute'), + interval: moment.duration(10, 'minute'), + boundLabel: i18n.translate('data.search.timeBuckets.minuteLabel', { + defaultMessage: '{amount, plural, one {a minute} other {# minutes}}', + values: { amount: 20 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.minuteLabel', { + defaultMessage: '{amount, plural, one {a minute} other {# minutes}}', + values: { amount: 10 }, + }), }, { - bound: Number(moment.duration(9, 'minute')), - interval: Number(moment.duration(5, 'minute')), + bound: moment.duration(9, 'minute'), + interval: moment.duration(5, 'minute'), + boundLabel: i18n.translate('data.search.timeBuckets.minuteLabel', { + defaultMessage: '{amount, plural, one {a minute} other {# minutes}}', + values: { amount: 9 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.minuteLabel', { + defaultMessage: '{amount, plural, one {a minute} other {# minutes}}', + values: { amount: 5 }, + }), }, { - bound: Number(moment.duration(3, 'minute')), - interval: Number(moment.duration(1, 'minute')), + bound: moment.duration(3, 'minute'), + interval: moment.duration(1, 'minute'), + boundLabel: i18n.translate('data.search.timeBuckets.minuteLabel', { + defaultMessage: '{amount, plural, one {a minute} other {# minutes}}', + values: { amount: 3 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.minuteLabel', { + defaultMessage: '{amount, plural, one {a minute} other {# minutes}}', + values: { amount: 1 }, + }), }, { - bound: Number(moment.duration(45, 'second')), - interval: Number(moment.duration(30, 'second')), + bound: moment.duration(45, 'second'), + interval: moment.duration(30, 'second'), + boundLabel: i18n.translate('data.search.timeBuckets.secondLabel', { + defaultMessage: '{amount, plural, one {a second} other {# seconds}}', + values: { amount: 45 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.secondLabel', { + defaultMessage: '{amount, plural, one {a second} other {# seconds}}', + values: { amount: 30 }, + }), }, { - bound: Number(moment.duration(15, 'second')), - interval: Number(moment.duration(10, 'second')), + bound: moment.duration(15, 'second'), + interval: moment.duration(10, 'second'), + boundLabel: i18n.translate('data.search.timeBuckets.secondLabel', { + defaultMessage: '{amount, plural, one {a second} other {# seconds}}', + values: { amount: 15 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.secondLabel', { + defaultMessage: '{amount, plural, one {a second} other {# seconds}}', + values: { amount: 10 }, + }), }, { - bound: Number(moment.duration(7.5, 'second')), - interval: Number(moment.duration(5, 'second')), + bound: moment.duration(7.5, 'second'), + interval: moment.duration(5, 'second'), + boundLabel: i18n.translate('data.search.timeBuckets.secondLabel', { + defaultMessage: '{amount, plural, one {a second} other {# seconds}}', + values: { amount: 7.5 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.secondLabel', { + defaultMessage: '{amount, plural, one {a second} other {# seconds}}', + values: { amount: 5 }, + }), }, { - bound: Number(moment.duration(5, 'second')), - interval: Number(moment.duration(1, 'second')), + bound: moment.duration(5, 'second'), + interval: moment.duration(1, 'second'), + boundLabel: i18n.translate('data.search.timeBuckets.secondLabel', { + defaultMessage: '{amount, plural, one {a second} other {# seconds}}', + values: { amount: 5 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.secondLabel', { + defaultMessage: '{amount, plural, one {a second} other {# seconds}}', + values: { amount: 1 }, + }), }, { - bound: Number(moment.duration(500, 'ms')), - interval: Number(moment.duration(100, 'ms')), + bound: moment.duration(500, 'ms'), + interval: moment.duration(100, 'ms'), + boundLabel: i18n.translate('data.search.timeBuckets.millisecondLabel', { + defaultMessage: '{amount, plural, one {a millisecond} other {# milliseconds}}', + values: { amount: 500 }, + }), + intervalLabel: i18n.translate('data.search.timeBuckets.millisecondLabel', { + defaultMessage: '{amount, plural, one {a millisecond} other {# milliseconds}}', + values: { amount: 100 }, + }), }, ]; +const boundsDescending = boundsDescendingRaw.map(({ bound, interval }) => ({ + bound: Number(bound), + interval: Number(interval), +})); + function getPerBucketMs(count: number, duration: number) { const ms = duration / count; return isFinite(ms) ? ms : NaN; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 9f0a5b64bde5a..ff3e2ebc89a41 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -307,6 +307,7 @@ import { parseEsInterval, parseInterval, toAbsoluteDates, + boundsDescendingRaw, // expressions utils getRequestInspectorStats, getResponseInspectorStats, @@ -416,6 +417,7 @@ export const search = { siblingPipelineType, termsAggFilter, toAbsoluteDates, + boundsDescendingRaw, }, getRequestInspectorStats, getResponseInspectorStats, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index dd24b1152b22c..e521e468d14a4 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2205,6 +2205,17 @@ export const search: { siblingPipelineType: string; termsAggFilter: string[]; toAbsoluteDates: typeof toAbsoluteDates; + boundsDescendingRaw: ({ + bound: number; + interval: import("moment").Duration; + boundLabel: string; + intervalLabel: string; + } | { + bound: import("moment").Duration; + interval: import("moment").Duration; + boundLabel: string; + intervalLabel: string; + })[]; }; getRequestInspectorStats: typeof getRequestInspectorStats; getResponseInspectorStats: typeof getResponseInspectorStats; @@ -2608,21 +2619,21 @@ export const UI_SETTINGS: { // 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 "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:398:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:417:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:425:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:426: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:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/search/session/session_service.ts:41:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts 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 1bdffc90797ac..dc7b291b7120f 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 @@ -299,6 +299,17 @@ export function DimensionEditor(props: DimensionEditorProps) { } ); + // Need to workout early on the error to decide whether to show this or an help text + const fieldErrorMessage = + (selectedOperationDefinition?.input !== 'fullReference' || + (incompleteOperation && operationDefinitionMap[incompleteOperation].input === 'field')) && + getErrorMessage( + selectedColumn, + Boolean(incompleteOperation), + selectedOperationDefinition?.input, + currentFieldIsInvalid + ); + return (
@@ -342,6 +353,11 @@ export function DimensionEditor(props: DimensionEditorProps) { existingFields={state.existingFields} selectionStyle={selectedOperationDefinition.selectionStyle} dateRange={dateRange} + labelAppend={selectedOperationDefinition?.getHelpMessage?.({ + data: props.data, + uiSettings: props.uiSettings, + currentColumn: state.layers[layerId].columns[columnId], + })} {...services} /> ); @@ -360,12 +376,15 @@ export function DimensionEditor(props: DimensionEditorProps) { })} fullWidth isInvalid={Boolean(incompleteOperation || currentFieldIsInvalid)} - error={getErrorMessage( - selectedColumn, - Boolean(incompleteOperation), - selectedOperationDefinition?.input, - currentFieldIsInvalid - )} + error={fieldErrorMessage} + labelAppend={ + !fieldErrorMessage && + selectedOperationDefinition?.getHelpMessage?.({ + data: props.data, + uiSettings: props.uiSettings, + currentColumn: state.layers[layerId].columns[columnId], + }) + } > { id: 'bytes', title: 'Bytes', }), + deserialize: jest.fn().mockReturnValue({ + convert: () => 'formatted', + }), } as unknown) as DataPublicPluginStart['fieldFormats'], } as unknown) as DataPublicPluginStart, core: {} as CoreSetup, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx index d73530ec8a920..1a394584360ad 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx @@ -8,7 +8,13 @@ import './dimension_editor.scss'; import _ from 'lodash'; import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFormRow, EuiSpacer, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import { + EuiFormRow, + EuiFormRowProps, + EuiSpacer, + EuiComboBox, + EuiComboBoxOptionOption, +} from '@elastic/eui'; import type { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; @@ -40,6 +46,7 @@ export interface ReferenceEditorProps { currentIndexPattern: IndexPattern; existingFields: IndexPatternPrivateState['existingFields']; dateRange: DateRange; + labelAppend?: EuiFormRowProps['labelAppend']; // Services uiSettings: IUiSettingsClient; @@ -59,6 +66,7 @@ export function ReferenceEditor(props: ReferenceEditorProps) { validation, selectionStyle, dateRange, + labelAppend, ...services } = props; @@ -251,6 +259,7 @@ export function ReferenceEditor(props: ReferenceEditorProps) { })} fullWidth isInvalid={showFieldInvalid} + labelAppend={labelAppend} > { + return ( + + + + + {children} + + + ); +}; + +export const HelpPopover = ({ + anchorPosition, + button, + children, + closePopover, + isOpen, + title, +}: { + anchorPosition?: EuiPopoverProps['anchorPosition']; + button: EuiPopoverProps['button']; + children: ReactNode; + closePopover: EuiPopoverProps['closePopover']; + isOpen: EuiPopoverProps['isOpen']; + title?: string; +}) => { + return ( + + {title && {title}} + + + {children} + + + ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx index d9805b337c000..d43dbccd92f83 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx @@ -5,10 +5,9 @@ */ import { i18n } from '@kbn/i18n'; -import { useState } from 'react'; -import React from 'react'; -import { EuiFormRow } from '@elastic/eui'; -import { EuiFieldNumber } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useState } from 'react'; +import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; import { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from '../column_types'; import { IndexPatternLayer } from '../../../types'; import { @@ -21,6 +20,7 @@ import { import { updateColumnParam } from '../../layer_helpers'; import { isValidNumber, useDebounceWithOptions } from '../helpers'; import { adjustTimeScaleOnOtherColumnChange } from '../../time_scale_utils'; +import { HelpPopover, HelpPopoverButton } from '../../../help_popover'; import type { OperationDefinition, ParamEditorProps } from '..'; const ofName = buildLabelFunction((name?: string) => { @@ -111,6 +111,7 @@ export const movingAverageOperation: OperationDefinition< }) ); }, + getHelpMessage: () => , getDisabledStatus(indexPattern, layer) { return checkForDateHistogram( layer, @@ -168,3 +169,79 @@ function MovingAverageParamEditor({ ); } + +const MovingAveragePopup = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + return ( + setIsPopoverOpen(!isPopoverOpen)}> + {i18n.translate('xpack.lens.indexPattern.movingAverage.helpText', { + defaultMessage: 'How it works', + })} + + } + closePopover={() => setIsPopoverOpen(false)} + isOpen={isPopoverOpen} + title={i18n.translate('xpack.lens.indexPattern.movingAverage.titleHelp', { + defaultMessage: 'How moving average works', + })} + > +

+ +

+ +

+ +

+ +

+ +

+ +
    +
  • (1 + 2 + 3 + 4 + 5) / 5 = 3
  • +
  • (2 + 3 + 4 + 5 + 6) / 5 = 4
  • +
  • ...
  • +
  • (5 + 6 + 7 + 8 + 9) / 5 = 7
  • +
+ +

+ +

+

+ +

+
    +
  • (1 + 2) / 2 = 1.5
  • +
  • (1 + 2 + 3) / 3 = 2
  • +
  • (1 + 2 + 3 + 4) / 4 = 2.5
  • +
  • (1 + 2 + 3 + 4 + 5) / 5 = 3
  • +
+ +

+ +

+
+ ); +}; 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 a41cc88c4f292..2e61f4fc3e24d 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 @@ -4,31 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { + EuiBasicTable, + EuiCode, + EuiFieldNumber, + EuiFlexGroup, + EuiFlexItem, EuiFormRow, + EuiSelect, + EuiSpacer, EuiSwitch, EuiSwitchEvent, - EuiFieldNumber, - EuiSelect, - EuiFlexItem, - EuiFlexGroup, EuiTextColor, - EuiSpacer, } from '@elastic/eui'; import { updateColumnParam } from '../layer_helpers'; import { OperationDefinition } from './index'; import { FieldBasedIndexPatternColumn } from './column_types'; import { AggFunctionsMapping, + DataPublicPluginStart, IndexPatternAggRestrictions, search, + UI_SETTINGS, } from '../../../../../../../src/plugins/data/public'; import { buildExpressionFunction } from '../../../../../../../src/plugins/expressions/public'; import { getInvalidFieldMessage, getSafeName } from './helpers'; +import { HelpPopover, HelpPopoverButton } from '../../help_popover'; const { isValidInterval } = search.aggs; const autoInterval = 'auto'; @@ -54,6 +59,7 @@ export const dateHistogramOperation: OperationDefinition< priority: 5, // Highest priority level used getErrorMessage: (layer, columnId, indexPattern) => getInvalidFieldMessage(layer.columns[columnId] as FieldBasedIndexPatternColumn, indexPattern), + getHelpMessage: (props) => , getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { if ( type === 'date' && @@ -334,3 +340,77 @@ function restrictedInterval(aggregationRestrictions?: Partial { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const infiniteBound = i18n.translate('xpack.lens.indexPattern.dateHistogram.moreThanYear', { + defaultMessage: 'More than a year', + }); + const upToLabel = i18n.translate('xpack.lens.indexPattern.dateHistogram.upTo', { + defaultMessage: 'Up to', + }); + + return ( + setIsPopoverOpen(!isPopoverOpen)}> + {i18n.translate('xpack.lens.indexPattern.dateHistogram.autoHelpText', { + defaultMessage: 'How it works', + })} + + } + closePopover={() => setIsPopoverOpen(false)} + isOpen={isPopoverOpen} + title={i18n.translate('xpack.lens.indexPattern.dateHistogram.titleHelp', { + defaultMessage: 'How auto date histogram works', + })} + > +

+ {i18n.translate('xpack.lens.indexPattern.dateHistogram.autoBasicExplanation', { + defaultMessage: 'The auto date histogram splits a date field into buckets by interval.', + })} +

+ +

+ {UI_SETTINGS.HISTOGRAM_MAX_BARS}, + targetBarSetting: {UI_SETTINGS.HISTOGRAM_BAR_TARGET}, + }} + /> +

+ +

+ {i18n.translate('xpack.lens.indexPattern.dateHistogram.autoAdvancedExplanation', { + defaultMessage: 'The interval follows this logic:', + })} +

+ + ({ + bound: typeof bound === 'number' ? infiniteBound : `${upToLabel} ${boundLabel}`, + interval: intervalLabel, + }))} + columns={[ + { + field: 'bound', + name: i18n.translate('xpack.lens.indexPattern.dateHistogram.autoBoundHeader', { + defaultMessage: 'Target interval measured', + }), + }, + { + field: 'interval', + name: i18n.translate('xpack.lens.indexPattern.dateHistogram.autoIntervalHeader', { + defaultMessage: 'Interval used', + }), + }, + ]} + /> +
+ ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 36c9cf75d2b6c..7dbc7d3b986a5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -126,6 +126,12 @@ export interface ParamEditorProps { data: DataPublicPluginStart; } +export interface HelpProps { + currentColumn: C; + uiSettings: IUiSettingsClient; + data: DataPublicPluginStart; +} + export type TimeScalingMode = 'disabled' | 'mandatory' | 'optional'; interface BaseOperationDefinitionProps { @@ -201,6 +207,8 @@ interface BaseOperationDefinitionProps { * If set to optional, time scaling won't be enabled by default and can be removed. */ timeScalingMode?: TimeScalingMode; + + getHelpMessage?: (props: HelpProps) => React.ReactNode; } interface BaseBuildColumnArgs { 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 index df955be6b490a..ad5c146ff6624 100644 --- 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 @@ -6,21 +6,73 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonEmpty, + EuiButtonIcon, + EuiCode, + EuiFlexGroup, + EuiFlexItem, EuiFormRow, EuiRange, - EuiFlexItem, - EuiFlexGroup, - EuiButtonIcon, EuiToolTip, - EuiIconTip, } from '@elastic/eui'; -import { IFieldFormat } from 'src/plugins/data/public'; +import type { IFieldFormat } from 'src/plugins/data/public'; +import { UI_SETTINGS } 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'; import { useDebounceWithOptions } from '../helpers'; +import { HelpPopover, HelpPopoverButton } from '../../../help_popover'; + +const GranularityHelpPopover = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + return ( + setIsPopoverOpen(!isPopoverOpen)}> + {i18n.translate('xpack.lens.indexPattern.ranges.granularityHelpText', { + defaultMessage: 'How it works', + })} + + } + closePopover={() => setIsPopoverOpen(false)} + isOpen={isPopoverOpen} + title={i18n.translate('xpack.lens.indexPattern.ranges.granularityPopoverTitle', { + defaultMessage: 'How granularity interval works', + })} + > +

+ {i18n.translate('xpack.lens.indexPattern.ranges.granularityPopoverBasicExplanation', { + defaultMessage: + 'Interval granularity divides the field into evenly spaced intervals based on the minimum and maximum values for the field.', + })} +

+ +

+ {UI_SETTINGS.HISTOGRAM_MAX_BARS}, + }} + /> +

+ +

+ {i18n.translate('xpack.lens.indexPattern.ranges.granularityPopoverAdvancedExplanation', { + defaultMessage: + 'Intervals are incremented by 10, 5 or 2: for example an interval can be 100 or 0.2 .', + })} +

+
+ ); +}; const BaseRangeEditor = ({ maxBars, @@ -49,12 +101,7 @@ const BaseRangeEditor = ({ const granularityLabel = i18n.translate('xpack.lens.indexPattern.ranges.granularity', { defaultMessage: 'Intervals granularity', }); - const granularityLabelDescription = i18n.translate( - 'xpack.lens.indexPattern.ranges.granularityDescription', - { - defaultMessage: 'Divides the field into evenly spaced intervals.', - } - ); + const decreaseButtonLabel = i18n.translate('xpack.lens.indexPattern.ranges.decreaseButtonLabel', { defaultMessage: 'Decrease granularity', }); @@ -65,21 +112,12 @@ const BaseRangeEditor = ({ return ( <> - {granularityLabel}{' '} - - - } + label={granularityLabel} data-test-subj="indexPattern-ranges-section-label" labelType="legend" fullWidth display="rowCompressed" + labelAppend={} > diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss index b9ff6a56d8e35..a2caeb93477fa 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss @@ -1,3 +1,3 @@ .lnsXyToolbar__popover { - width: 320px; + width: 365px; } 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 351b1f0d71651..b8bca09bb353c 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 @@ -21,6 +21,7 @@ import { EuiColorPickerProps, EuiToolTip, EuiIcon, + EuiIconTip, } from '@elastic/eui'; import { PaletteRegistry } from 'src/plugins/charts/public'; import { @@ -327,9 +328,25 @@ export function XyToolbar(props: VisualizationToolbarProps) { {isFittingEnabled ? ( + {i18n.translate('xpack.lens.xyChart.missingValuesLabel', { + defaultMessage: 'Missing values', + })}{' '} + + + } > Date: Wed, 20 Jan 2021 10:52:03 -0600 Subject: [PATCH 14/83] [ML] Redesign file-based Data Visualizer (#87598) --- .../ml/common/types/file_datavisualizer.ts | 18 +- .../ml/common/types/ml_url_generator.ts | 13 +- .../components/data_grid/column_chart.scss | 1 + .../components/data_grid/use_column_chart.tsx | 2 +- .../field_title_bar/field_title_bar.tsx | 12 +- .../analytics_list/use_table_settings.ts | 10 +- .../application/datavisualizer/_index.scss | 2 +- .../file_based/components/_index.scss | 1 - .../expanded_row/file_based_expanded_row.tsx} | 49 ++--- .../components/expanded_row/index.ts | 7 + .../components/field_data_row/index.ts | 7 + .../field_data_row/number_content_preview.tsx | 55 +++++ .../field_names_filter/field_names_filter.tsx | 46 ++++ .../components/field_names_filter}/index.ts | 2 +- .../field_types_filter/field_types_filter.tsx | 79 +++++++ .../components/field_types_filter/index.ts | 7 + .../components/fields_stats/_index.scss | 2 - .../fields_stats/field_stats_card.js | 184 ---------------- .../components/fields_stats/fields_stats.js | 113 ---------- .../fields_stats_grid/create_fields.ts | 126 +++++++++++ .../fields_stats_grid/fields_stats_grid.tsx | 124 +++++++++++ .../fields_stats_grid/filter_fields.ts | 36 ++++ .../get_field_names.ts} | 28 ++- .../index.js => fields_stats_grid/index.ts} | 2 +- .../file_datavisualizer_view.js | 1 - .../components/results_view/results_view.tsx | 26 +-- .../datavisualizer/index_based/_index.scss | 2 +- .../index_based/common/index.ts | 1 - .../components/expanded_row}/expanded_row.tsx | 15 +- .../components/expanded_row/index.ts | 7 + .../field_count_panel/field_count_panel.tsx | 99 ++------- .../content_types/boolean_content.tsx | 84 -------- .../content_types/geo_point_content.tsx | 41 ---- .../content_types/ip_content.tsx | 34 --- .../content_types/keyword_content.tsx | 29 --- .../content_types/number_content.tsx | 200 ------------------ .../content_types/other_content.tsx | 83 -------- .../content_types/text_content.tsx | 62 ------ .../_index.scss | 1 - .../content_types/document_count_content.tsx | 4 +- .../field_data_row/content_types/index.ts | 8 + .../content_types/not_in_docs_content.tsx | 0 .../document_count_chart.tsx | 2 - .../document_count_chart/index.ts | 0 .../examples_list/examples_list.tsx | 2 +- .../examples_list/index.ts | 0 .../loading_indicator/index.ts | 0 .../loading_indicator/loading_indicator.tsx | 0 .../top_values/_top_values.scss | 0 .../top_values/index.ts | 0 .../top_values/top_values.tsx | 0 .../components/search_panel/search_panel.tsx | 2 - .../datavisualizer/index_based/page.tsx | 38 +++- .../_field_data_row.scss} | 0 .../_index.scss | 5 + .../expanded_row_field_header.tsx | 0 .../expanded_row_field_header/index.ts | 0 .../components/field_count_stats/_index.scss | 3 + .../components/field_count_stats/index.ts | 12 ++ .../field_count_stats/metric_fields_count.tsx | 67 ++++++ .../field_count_stats/total_fields_count.tsx | 66 ++++++ .../field_data_expanded_row/_index.scss | 0 .../_number_content.scss | 0 .../boolean_content.tsx | 143 +++++++++++++ .../field_data_expanded_row}/date_content.tsx | 40 ++-- .../document_stats.tsx | 91 ++++++++ .../geo_point_content.tsx | 35 +++ .../field_data_expanded_row}/index.ts | 2 - .../field_data_expanded_row/ip_content.tsx | 38 ++++ .../keyword_content.tsx | 35 +++ .../number_content.tsx | 20 +- .../field_data_expanded_row/other_content.tsx | 22 ++ .../field_data_expanded_row/text_content.tsx | 64 ++++++ .../boolean_content_preview.tsx | 40 ++++ .../field_data_row/distinct_values.tsx | 0 .../field_data_row/document_stats.tsx | 4 +- .../components/field_data_row}/index.ts | 2 +- .../field_data_row/number_content_preview.tsx | 10 +- .../field_data_row/top_values_preview.tsx | 4 +- .../metric_distribution_chart/index.ts | 0 .../metric_distribution_chart.tsx | 55 +---- ...metric_distribution_chart_data_builder.tsx | 0 ...tric_distribution_chart_tooltip_header.tsx | 2 +- .../data_visualizer_stats_table.tsx} | 87 +++++--- .../datavisualizer/stats_table/hooks/index.ts | 7 + .../hooks/use_data_viz_chart_theme.ts | 54 +++++ .../datavisualizer/stats_table/index.ts | 7 + .../stats_table/types/field_data_row.ts | 11 + .../types}/field_vis_config.ts | 27 ++- .../datavisualizer/stats_table/types/index.ts | 15 ++ .../datavisualizer/stats_table/utils.ts | 37 ++++ .../formatters/round_to_decimal_place.ts | 3 +- .../translations/translations/ja-JP.json | 17 -- .../translations/translations/zh-CN.json | 17 -- .../data_visualizer/file_data_visualizer.ts | 149 ++++++++++++- .../files_to_import/artificial_server_log | 39 ++-- .../data_visualizer/index_data_visualizer.ts | 2 +- .../services/ml/data_visualizer_table.ts | 21 +- 98 files changed, 1721 insertions(+), 1199 deletions(-) rename x-pack/plugins/ml/public/application/datavisualizer/{index_based/components/field_data_card/field_data_card.tsx => file_based/components/expanded_row/file_based_expanded_row.tsx} (52%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/index.ts create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/index.ts create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/number_content_preview.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/field_names_filter.tsx rename x-pack/plugins/ml/public/application/datavisualizer/{index_based/components/field_data_card => file_based/components/field_names_filter}/index.ts (77%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/field_types_filter.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/index.ts delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_index.scss delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/field_stats_card.js delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/create_fields.ts create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/fields_stats_grid.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/filter_fields.ts rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/{fields_stats/get_field_names.js => fields_stats_grid/get_field_names.ts} (53%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/{fields_stats/index.js => fields_stats_grid/index.ts} (81%) rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => index_based/components/expanded_row}/expanded_row.tsx (75%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/index.ts delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/boolean_content.tsx delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/geo_point_content.tsx delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/ip_content.tsx delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/keyword_content.tsx delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/other_content.tsx delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/text_content.tsx rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/_index.scss (55%) rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/content_types/document_count_content.tsx (90%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/index.ts rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/content_types/not_in_docs_content.tsx (100%) rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/document_count_chart/document_count_chart.tsx (98%) rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/document_count_chart/index.ts (100%) rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/examples_list/examples_list.tsx (93%) rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/examples_list/index.ts (100%) rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/loading_indicator/index.ts (100%) rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/loading_indicator/loading_indicator.tsx (100%) rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/top_values/_top_values.scss (100%) rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/top_values/index.ts (100%) rename x-pack/plugins/ml/public/application/datavisualizer/index_based/components/{field_data_card => field_data_row}/top_values/top_values.tsx (100%) rename x-pack/plugins/ml/public/application/datavisualizer/{index_based/components/field_data_card/_field_data_card.scss => stats_table/_field_data_row.scss} (100%) rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table}/_index.scss (87%) rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table}/components/expanded_row_field_header/expanded_row_field_header.tsx (100%) rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table}/components/expanded_row_field_header/index.ts (100%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/_index.scss create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/index.ts create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/metric_fields_count.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/total_fields_count.tsx rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table}/components/field_data_expanded_row/_index.scss (100%) rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table}/components/field_data_expanded_row/_number_content.scss (100%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx rename x-pack/plugins/ml/public/application/datavisualizer/{index_based/components/field_data_card/content_types => stats_table/components/field_data_expanded_row}/date_content.tsx (58%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.tsx rename x-pack/plugins/ml/public/application/datavisualizer/{index_based/components/field_data_card/content_types => stats_table/components/field_data_expanded_row}/index.ts (83%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table}/components/field_data_expanded_row/number_content.tsx (89%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/boolean_content_preview.tsx rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table}/components/field_data_row/distinct_values.tsx (100%) rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table}/components/field_data_row/document_stats.tsx (86%) rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table/components/field_data_row}/index.ts (78%) rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table}/components/field_data_row/number_content_preview.tsx (91%) rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid => stats_table}/components/field_data_row/top_values_preview.tsx (89%) rename x-pack/plugins/ml/public/application/datavisualizer/{index_based/components/field_data_card => stats_table/components}/metric_distribution_chart/index.ts (100%) rename x-pack/plugins/ml/public/application/datavisualizer/{index_based/components/field_data_card => stats_table/components}/metric_distribution_chart/metric_distribution_chart.tsx (60%) rename x-pack/plugins/ml/public/application/datavisualizer/{index_based/components/field_data_card => stats_table/components}/metric_distribution_chart/metric_distribution_chart_data_builder.tsx (100%) rename x-pack/plugins/ml/public/application/datavisualizer/{index_based/components/field_data_card => stats_table/components}/metric_distribution_chart/metric_distribution_chart_tooltip_header.tsx (95%) rename x-pack/plugins/ml/public/application/datavisualizer/{stats_datagrid/stats_datagrid.tsx => stats_table/data_visualizer_stats_table.tsx} (76%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/hooks/index.ts create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/hooks/use_data_viz_chart_theme.ts create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/index.ts create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_data_row.ts rename x-pack/plugins/ml/public/application/datavisualizer/{index_based/common => stats_table/types}/field_vis_config.ts (69%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/index.ts create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/utils.ts diff --git a/x-pack/plugins/ml/common/types/file_datavisualizer.ts b/x-pack/plugins/ml/common/types/file_datavisualizer.ts index 9dc3896e9be48..b1967cfe83f3c 100644 --- a/x-pack/plugins/ml/common/types/file_datavisualizer.ts +++ b/x-pack/plugins/ml/common/types/file_datavisualizer.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ES_FIELD_TYPES } from '../../../../../src/plugins/data/common'; + export interface InputOverrides { [key: string]: string; } @@ -29,15 +31,27 @@ export interface FindFileStructureResponse { count: number; cardinality: number; top_hits: Array<{ count: number; value: any }>; + mean_value?: number; + median_value?: number; max_value?: number; min_value?: number; + earliest?: string; + latest?: string; }; }; sample_start: string; num_messages_analyzed: number; mappings: { - [fieldName: string]: { - type: string; + properties: { + [fieldName: string]: { + // including all possible Elasticsearch types + // since find_file_structure API can be enhanced to include new fields in the future + type: Exclude< + ES_FIELD_TYPES, + ES_FIELD_TYPES._ID | ES_FIELD_TYPES._INDEX | ES_FIELD_TYPES._SOURCE | ES_FIELD_TYPES._TYPE + >; + format?: string; + }; }; }; quote: string; diff --git a/x-pack/plugins/ml/common/types/ml_url_generator.ts b/x-pack/plugins/ml/common/types/ml_url_generator.ts index 3c70cf4c27b5d..3ff57fc622da4 100644 --- a/x-pack/plugins/ml/common/types/ml_url_generator.ts +++ b/x-pack/plugins/ml/common/types/ml_url_generator.ts @@ -42,11 +42,7 @@ export interface MlGenericUrlPageState extends MlIndexBasedSearchState { [key: string]: any; } -export interface DataVisualizerIndexBasedAppState { - pageIndex: number; - pageSize: number; - sortField: string; - sortDirection: string; +export interface DataVisualizerIndexBasedAppState extends Omit { searchString?: Query['query']; searchQuery?: Query['query']; searchQueryLanguage?: SearchQueryLanguage; @@ -57,6 +53,13 @@ export interface DataVisualizerIndexBasedAppState { showAllFields?: boolean; showEmptyFields?: boolean; } + +export interface DataVisualizerFileBasedAppState extends Omit { + visibleFieldTypes?: string[]; + visibleFieldNames?: string[]; + showDistributions?: boolean; +} + export type MlGenericUrlState = MLPageState< | typeof ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER | typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss index e07c8a7b81692..756804a0e6aa0 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss @@ -22,6 +22,7 @@ .mlDataGridChart__legendBoolean { width: 100%; + min-width: $euiButtonMinWidth; td { text-align: center } } diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx index bb52941f463fe..2ecbe0601816a 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx @@ -20,7 +20,7 @@ import { NON_AGGREGATABLE } from './common'; export const hoveredRow$ = new BehaviorSubject(null); -const BAR_COLOR = euiPaletteColorBlind()[0]; +export const BAR_COLOR = euiPaletteColorBlind()[0]; const BAR_COLOR_BLUR = euiPaletteColorBlind({ rotations: 2 })[10]; const MAX_CHART_COLUMNS = 20; diff --git a/x-pack/plugins/ml/public/application/components/field_title_bar/field_title_bar.tsx b/x-pack/plugins/ml/public/application/components/field_title_bar/field_title_bar.tsx index 0e98a23637f03..dec149cdec403 100644 --- a/x-pack/plugins/ml/public/application/components/field_title_bar/field_title_bar.tsx +++ b/x-pack/plugins/ml/public/application/components/field_title_bar/field_title_bar.tsx @@ -11,11 +11,15 @@ import { EuiText, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FieldTypeIcon } from '../field_type_icon'; -import { FieldVisConfig } from '../../datavisualizer/index_based/common'; import { getMLJobTypeAriaLabel } from '../../util/field_types_utils'; +import { + FieldVisConfig, + FileBasedFieldVisConfig, + isIndexBasedFieldVisConfig, +} from '../../datavisualizer/stats_table/types/field_vis_config'; interface Props { - card: FieldVisConfig; + card: FieldVisConfig | FileBasedFieldVisConfig; } export const FieldTitleBar: FC = ({ card }) => { @@ -30,13 +34,13 @@ export const FieldTitleBar: FC = ({ card }) => { if (card.fieldName === undefined) { classNames.push('document_count'); - } else if (card.isUnsupportedType === true) { + } else if (isIndexBasedFieldVisConfig(card) && card.isUnsupportedType === true) { classNames.push('type-other'); } else { classNames.push(card.type); } - if (card.isUnsupportedType !== true) { + if (isIndexBasedFieldVisConfig(card) && card.isUnsupportedType !== true) { // All the supported field types have aria labels. cardTitleAriaLabel.unshift(getMLJobTypeAriaLabel(card.type)!); } 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 da8e272103f3d..d74ed4447cfe9 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 @@ -7,7 +7,10 @@ import { Direction, EuiBasicTableProps, Pagination, PropertySort } from '@elastic/eui'; import { useCallback, useMemo } from 'react'; import { ListingPageUrlState } from '../../../../../../../common/types/common'; -import { DataVisualizerIndexBasedAppState } from '../../../../../../../common/types/ml_url_generator'; +import { + DataVisualizerFileBasedAppState, + DataVisualizerIndexBasedAppState, +} from '../../../../../../../common/types/ml_url_generator'; const PAGE_SIZE_OPTIONS = [10, 25, 50]; @@ -38,7 +41,10 @@ interface UseTableSettingsReturnValue { export function useTableSettings( items: TypeOfItem[], - pageState: ListingPageUrlState | DataVisualizerIndexBasedAppState, + pageState: + | ListingPageUrlState + | DataVisualizerIndexBasedAppState + | DataVisualizerFileBasedAppState, updatePageState: (update: Partial) => void ): UseTableSettingsReturnValue { const { pageIndex, pageSize, sortField, sortDirection } = pageState; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/_index.scss index 081f8b971432e..195eeea72baa0 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/_index.scss @@ -1,3 +1,3 @@ @import 'file_based/index'; @import 'index_based/index'; -@import 'stats_datagrid/index'; +@import 'stats_table/index'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/_index.scss index 42974d098bda4..a7c3926407ea0 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/_index.scss @@ -1,7 +1,6 @@ @import 'file_datavisualizer_view/index'; @import 'results_view/index'; @import 'analysis_summary/index'; -@import 'fields_stats/index'; @import 'about_panel/index'; @import 'import_summary/index'; @import 'experimental_badge/index'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/field_data_card.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx similarity index 52% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/field_data_card.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx index a568356a06d26..77f31ae9c2322 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/field_data_card.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx @@ -4,45 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiPanel } from '@elastic/eui'; -import React, { FC } from 'react'; - -import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; - -import { FieldVisConfig } from '../../common'; -import { FieldTitleBar } from '../../../../components/field_title_bar/index'; +import React from 'react'; import { BooleanContent, DateContent, GeoPointContent, IpContent, KeywordContent, - NotInDocsContent, - NumberContent, OtherContent, TextContent, -} from './content_types'; -import { LoadingIndicator } from './loading_indicator'; - -export interface FieldDataCardProps { - config: FieldVisConfig; -} + NumberContent, +} from '../../../stats_table/components/field_data_expanded_row'; +import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; +import type { FileBasedFieldVisConfig } from '../../../stats_table/types/field_vis_config'; -export const FieldDataCard: FC = ({ config }) => { - const { fieldName, loading, type, existsInDocs } = config; +export const FileBasedDataVisualizerExpandedRow = ({ item }: { item: FileBasedFieldVisConfig }) => { + const config = item; + const { type, fieldName } = config; function getCardContent() { - if (existsInDocs === false) { - return ; - } - switch (type) { case ML_JOB_FIELD_TYPES.NUMBER: - if (fieldName !== undefined) { - return ; - } else { - return null; - } + return ; case ML_JOB_FIELD_TYPES.BOOLEAN: return ; @@ -68,15 +51,11 @@ export const FieldDataCard: FC = ({ config }) => { } return ( - - -
- {loading === true ? : getCardContent()} -
-
+ {getCardContent()} +
); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/index.ts new file mode 100644 index 0000000000000..0601f739ed81b --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/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 { FileBasedDataVisualizerExpandedRow } from './file_based_expanded_row'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/index.ts new file mode 100644 index 0000000000000..2a7eab9beb22f --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/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 { FileBasedNumberContentPreview } from './number_content_preview'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/number_content_preview.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/number_content_preview.tsx new file mode 100644 index 0000000000000..de6d129e0b462 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_data_row/number_content_preview.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 React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FileBasedFieldVisConfig } from '../../../stats_table/types'; + +export const FileBasedNumberContentPreview = ({ config }: { config: FileBasedFieldVisConfig }) => { + const stats = config.stats; + if ( + stats === undefined || + stats.min === undefined || + stats.median === undefined || + stats.max === undefined + ) + return null; + return ( + + + + + + + + + + + + + + + + + + + + {stats.min} + {stats.median} + {stats.max} + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/field_names_filter.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/field_names_filter.tsx new file mode 100644 index 0000000000000..afc0a95e7f59b --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/field_names_filter.tsx @@ -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 React, { FC, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { MultiSelectPicker } from '../../../../components/multi_select_picker'; +import type { + FileBasedFieldVisConfig, + FileBasedUnknownFieldVisConfig, +} from '../../../stats_table/types/field_vis_config'; + +interface Props { + fields: Array; + setVisibleFieldNames(q: string[]): void; + visibleFieldNames: string[]; +} + +export const DataVisualizerFieldNamesFilter: FC = ({ + fields, + setVisibleFieldNames, + visibleFieldNames, +}) => { + const fieldNameTitle = useMemo( + () => + i18n.translate('xpack.ml.dataVisualizer.fileBased.fieldNameSelect', { + defaultMessage: 'Field name', + }), + [] + ); + const options = useMemo( + () => fields.filter((d) => d.fieldName !== undefined).map((d) => ({ value: d.fieldName! })), + [fields] + ); + + return ( + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/index.ts similarity index 77% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/index.ts rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/index.ts index 9b7939c90c71d..1bd19e27d3b9f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/index.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_names_filter/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { FieldDataCard, FieldDataCardProps } from './field_data_card'; +export { DataVisualizerFieldNamesFilter } from './field_names_filter'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/field_types_filter.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/field_types_filter.tsx new file mode 100644 index 0000000000000..f52a588844cdf --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/field_types_filter.tsx @@ -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 React, { FC, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { MultiSelectPicker, Option } from '../../../../components/multi_select_picker'; +import type { + FileBasedFieldVisConfig, + FileBasedUnknownFieldVisConfig, +} from '../../../stats_table/types/field_vis_config'; +import { FieldTypeIcon } from '../../../../components/field_type_icon'; +import { ML_JOB_FIELD_TYPES_OPTIONS } from '../../../index_based/components/search_panel/field_type_filter'; + +interface Props { + fields: Array; + setVisibleFieldTypes(q: string[]): void; + visibleFieldTypes: string[]; +} + +export const DataVisualizerFieldTypesFilter: FC = ({ + fields, + setVisibleFieldTypes, + visibleFieldTypes, +}) => { + const fieldNameTitle = useMemo( + () => + i18n.translate('xpack.ml.dataVisualizer.fileBased.fieldTypeSelect', { + defaultMessage: 'Field type', + }), + [] + ); + + const options = useMemo(() => { + const fieldTypesTracker = new Set(); + const fieldTypes: Option[] = []; + fields.forEach(({ type }) => { + if ( + type !== undefined && + !fieldTypesTracker.has(type) && + ML_JOB_FIELD_TYPES_OPTIONS[type] !== undefined + ) { + const item = ML_JOB_FIELD_TYPES_OPTIONS[type]; + + fieldTypesTracker.add(type); + fieldTypes.push({ + value: type, + name: ( + + {item.name} + {type && ( + + + + )} + + ), + }); + } + }); + return fieldTypes; + }, [fields]); + return ( + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/index.ts new file mode 100644 index 0000000000000..1209fd94790c0 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/field_types_filter/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 { DataVisualizerFieldTypesFilter } from './field_types_filter'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_index.scss deleted file mode 100644 index fe6a232f016a3..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'fields_stats'; -@import 'field_stats_card'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/field_stats_card.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/field_stats_card.js deleted file mode 100644 index 2e9efa43f36bc..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/field_stats_card.js +++ /dev/null @@ -1,184 +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 { - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiPanel, - EuiProgress, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { FieldTypeIcon } from '../../../../components/field_type_icon'; -import { DisplayValue } from '../../../../components/display_value'; -import { getMLJobTypeAriaLabel } from '../../../../util/field_types_utils'; - -export function FieldStatsCard({ field }) { - let type = field.type; - if (type === 'double' || type === 'long') { - type = 'number'; - } - - const typeAriaLabel = getMLJobTypeAriaLabel(type); - const cardTitleAriaLabel = [field.name]; - if (typeAriaLabel) { - cardTitleAriaLabel.unshift(typeAriaLabel); - } - - return ( - -
-
- -
- {field.name} -
-
- -
- {field.count > 0 && ( - -
- - - - - - - - - - - - - - - - - - {field.median_value && ( - -
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
- -
-
-
- )} -
- - {field.top_hits && ( - - - -
-
- -
- {field.top_hits.map(({ count, value }) => { - const pcnt = Math.round((count / field.count) * 100 * 100) / 100; - return ( - - - - {value}  - - - - - - - - {pcnt}% - - - - ); - })} -
-
- )} -
- )} - {field.count === 0 && ( -
-
- -
-
- )} -
-
-
- ); -} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js deleted file mode 100644 index 785dd7db260fc..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js +++ /dev/null @@ -1,113 +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, { Component } from 'react'; - -import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui'; -import { FieldStatsCard } from './field_stats_card'; -import { getFieldNames } from './get_field_names'; -import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; -import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; - -export class FieldsStats extends Component { - constructor(props) { - super(props); - - this.state = { - fields: [], - }; - } - - componentDidMount() { - this.setState({ - fields: createFields(this.props.results), - }); - } - - render() { - return ( -
- - {this.state.fields.map((f) => ( - - - - ))} - -
- ); - } -} - -function createFields(results) { - const { - mappings, - field_stats: fieldStats, - num_messages_analyzed: numMessagesAnalyzed, - timestamp_field: timestampField, - } = results; - - if (mappings && mappings.properties && fieldStats) { - const fieldNames = getFieldNames(results); - - return fieldNames.map((name) => { - if (fieldStats[name] !== undefined) { - const field = { name }; - const f = fieldStats[name]; - const m = mappings.properties[name]; - - // sometimes the timestamp field is not in the mappings, and so our - // collection of fields will be missing a time field with a type of date - if (name === timestampField && field.type === undefined) { - field.type = ML_JOB_FIELD_TYPES.DATE; - } - - if (f !== undefined) { - Object.assign(field, f); - } - - if (m !== undefined) { - field.type = m.type; - if (m.format !== undefined) { - field.format = m.format; - } - } - - const percent = (field.count / numMessagesAnalyzed) * 100; - field.percent = roundToDecimalPlace(percent); - - // round min, max, median, mean to 2dp. - if (field.median_value !== undefined) { - field.median_value = roundToDecimalPlace(field.median_value); - field.mean_value = roundToDecimalPlace(field.mean_value); - field.min_value = roundToDecimalPlace(field.min_value); - field.max_value = roundToDecimalPlace(field.max_value); - } - - return field; - } else { - // field is not in the field stats - // this could be the message field for a semi-structured log file or a - // field which the endpoint has not been able to work out any information for - const type = - mappings.properties[name] && mappings.properties[name].type === ML_JOB_FIELD_TYPES.TEXT - ? ML_JOB_FIELD_TYPES.TEXT - : ML_JOB_FIELD_TYPES.UNKNOWN; - - return { - name, - type, - mean_value: 0, - count: 0, - cardinality: 0, - percent: 0, - }; - } - }); - } - - return []; -} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/create_fields.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/create_fields.ts new file mode 100644 index 0000000000000..6313656fc09c9 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/create_fields.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 { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; +import { getFieldNames, getSupportedFieldType } from './get_field_names'; +import { FileBasedFieldVisConfig } from '../../../stats_table/types'; +import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; +import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; + +export function createFields(results: FindFileStructureResponse) { + const { + mappings, + field_stats: fieldStats, + num_messages_analyzed: numMessagesAnalyzed, + timestamp_field: timestampField, + } = results; + + let numericFieldsCount = 0; + + if (mappings && mappings.properties && fieldStats) { + const fieldNames = getFieldNames(results); + + const items = fieldNames.map((name) => { + if (fieldStats[name] !== undefined) { + const field: FileBasedFieldVisConfig = { + fieldName: name, + type: ML_JOB_FIELD_TYPES.UNKNOWN, + }; + const f = fieldStats[name]; + const m = mappings.properties[name]; + + // sometimes the timestamp field is not in the mappings, and so our + // collection of fields will be missing a time field with a type of date + if (name === timestampField && field.type === ML_JOB_FIELD_TYPES.UNKNOWN) { + field.type = ML_JOB_FIELD_TYPES.DATE; + } + + if (m !== undefined) { + field.type = getSupportedFieldType(m.type); + if (field.type === ML_JOB_FIELD_TYPES.NUMBER) { + numericFieldsCount += 1; + } + if (m.format !== undefined) { + field.format = m.format; + } + } + + let _stats = {}; + + // round min, max, median, mean to 2dp. + if (f.median_value !== undefined) { + _stats = { + ..._stats, + median: roundToDecimalPlace(f.median_value), + mean: roundToDecimalPlace(f.mean_value), + min: roundToDecimalPlace(f.min_value), + max: roundToDecimalPlace(f.max_value), + }; + } + if (f.cardinality !== undefined) { + _stats = { + ..._stats, + cardinality: f.cardinality, + count: f.count, + sampleCount: numMessagesAnalyzed, + }; + } + + if (f.top_hits !== undefined) { + if (field.type === ML_JOB_FIELD_TYPES.TEXT) { + _stats = { + ..._stats, + examples: f.top_hits.map((hit) => hit.value), + }; + } else { + _stats = { + ..._stats, + topValues: f.top_hits.map((hit) => ({ key: hit.value, doc_count: hit.count })), + }; + } + } + + if (field.type === ML_JOB_FIELD_TYPES.DATE) { + _stats = { + ..._stats, + earliest: f.earliest, + latest: f.latest, + }; + } + + field.stats = _stats; + return field; + } else { + // field is not in the field stats + // this could be the message field for a semi-structured log file or a + // field which the endpoint has not been able to work out any information for + const type = + mappings.properties[name] && mappings.properties[name].type === ML_JOB_FIELD_TYPES.TEXT + ? ML_JOB_FIELD_TYPES.TEXT + : ML_JOB_FIELD_TYPES.UNKNOWN; + + return { + fieldName: name, + type, + stats: { + mean: 0, + count: 0, + sampleCount: numMessagesAnalyzed, + cardinality: 0, + }, + }; + } + }); + + return { + fields: items, + totalFieldsCount: items.length, + totalMetricFieldsCount: numericFieldsCount, + }; + } + + return { fields: [], totalFieldsCount: 0, totalMetricFieldsCount: 0 }; +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/fields_stats_grid.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/fields_stats_grid.tsx new file mode 100644 index 0000000000000..e2911653ab41a --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/fields_stats_grid.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo, FC } from 'react'; +import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import type { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; +import { DataVisualizerTable, ItemIdToExpandedRowMap } from '../../../stats_table'; +import type { FileBasedFieldVisConfig } from '../../../stats_table/types/field_vis_config'; +import { FileBasedDataVisualizerExpandedRow } from '../expanded_row'; + +import { DataVisualizerFieldNamesFilter } from '../field_names_filter'; +import { DataVisualizerFieldTypesFilter } from '../field_types_filter'; +import { createFields } from './create_fields'; +import { filterFields } from './filter_fields'; +import { usePageUrlState } from '../../../../util/url_state'; +import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator'; +import { + MetricFieldsCount, + TotalFieldsCount, +} from '../../../stats_table/components/field_count_stats'; +import type { DataVisualizerFileBasedAppState } from '../../../../../../common/types/ml_url_generator'; + +interface Props { + results: FindFileStructureResponse; +} +export const getDefaultDataVisualizerListState = (): Required => ({ + pageIndex: 0, + pageSize: 10, + sortField: 'fieldName', + sortDirection: 'asc', + visibleFieldTypes: [], + visibleFieldNames: [], + showDistributions: true, +}); + +function getItemIdToExpandedRowMap( + itemIds: string[], + items: FileBasedFieldVisConfig[] +): ItemIdToExpandedRowMap { + return itemIds.reduce((m: ItemIdToExpandedRowMap, fieldName: string) => { + const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName); + if (item !== undefined) { + m[fieldName] = ; + } + return m; + }, {} as ItemIdToExpandedRowMap); +} + +export const FieldsStatsGrid: FC = ({ results }) => { + const restorableDefaults = getDefaultDataVisualizerListState(); + const [ + dataVisualizerListState, + setDataVisualizerListState, + ] = usePageUrlState( + ML_PAGES.DATA_VISUALIZER_FILE, + restorableDefaults + ); + const visibleFieldTypes = + dataVisualizerListState.visibleFieldTypes ?? restorableDefaults.visibleFieldTypes; + const setVisibleFieldTypes = (values: string[]) => { + setDataVisualizerListState({ ...dataVisualizerListState, visibleFieldTypes: values }); + }; + + const visibleFieldNames = + dataVisualizerListState.visibleFieldNames ?? restorableDefaults.visibleFieldNames; + const setVisibleFieldNames = (values: string[]) => { + setDataVisualizerListState({ ...dataVisualizerListState, visibleFieldNames: values }); + }; + + const { fields, totalFieldsCount, totalMetricFieldsCount } = useMemo( + () => createFields(results), + [results, visibleFieldNames, visibleFieldTypes] + ); + const { filteredFields, visibleFieldsCount, visibleMetricsCount } = useMemo( + () => filterFields(fields, visibleFieldNames, visibleFieldTypes), + [results, visibleFieldNames, visibleFieldTypes] + ); + + const fieldsCountStats = { visibleFieldsCount, totalFieldsCount }; + const metricsStats = { visibleMetricsCount, totalMetricFieldsCount }; + + return ( +
+ + + + + + + + + + + + + + items={filteredFields} + pageState={dataVisualizerListState} + updatePageState={setDataVisualizerListState} + getItemIdToExpandedRowMap={getItemIdToExpandedRowMap} + /> +
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/filter_fields.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/filter_fields.ts new file mode 100644 index 0000000000000..9f3ec88507aef --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/filter_fields.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 { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; +import type { + FileBasedFieldVisConfig, + FileBasedUnknownFieldVisConfig, +} from '../../../stats_table/types/field_vis_config'; + +export function filterFields( + fields: Array, + visibleFieldNames: string[], + visibleFieldTypes: string[] +) { + let items = fields; + + if (visibleFieldTypes && visibleFieldTypes.length > 0) { + items = items.filter( + (config) => visibleFieldTypes.findIndex((field) => field === config.type) > -1 + ); + } + if (visibleFieldNames && visibleFieldNames.length > 0) { + items = items.filter((config) => { + return visibleFieldNames.findIndex((field) => field === config.fieldName) > -1; + }); + } + + return { + filteredFields: items, + visibleFieldsCount: items.length, + visibleMetricsCount: items.filter((d) => d.type === ML_JOB_FIELD_TYPES.NUMBER).length, + }; +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/get_field_names.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/get_field_names.ts similarity index 53% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/get_field_names.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/get_field_names.ts index c423dc3c63e39..e2f73505f6cd2 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/get_field_names.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/get_field_names.ts @@ -5,8 +5,11 @@ */ import { difference } from 'lodash'; - -export function getFieldNames(results) { +import type { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; +import { MlJobFieldType } from '../../../../../../common/types/field_types'; +import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; +import { ES_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/common'; +export function getFieldNames(results: FindFileStructureResponse) { const { mappings, field_stats: fieldStats, column_names: columnNames } = results; // if columnNames exists (i.e delimited) use it for the field list @@ -29,3 +32,24 @@ export function getFieldNames(results) { } return tempFields; } + +export function getSupportedFieldType(type: string): MlJobFieldType { + switch (type) { + case ES_FIELD_TYPES.FLOAT: + case ES_FIELD_TYPES.HALF_FLOAT: + case ES_FIELD_TYPES.SCALED_FLOAT: + case ES_FIELD_TYPES.DOUBLE: + case ES_FIELD_TYPES.INTEGER: + case ES_FIELD_TYPES.LONG: + case ES_FIELD_TYPES.SHORT: + case ES_FIELD_TYPES.UNSIGNED_LONG: + return ML_JOB_FIELD_TYPES.NUMBER; + + case ES_FIELD_TYPES.DATE: + case ES_FIELD_TYPES.DATE_NANOS: + return ML_JOB_FIELD_TYPES.DATE; + + default: + return type as MlJobFieldType; + } +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/index.ts similarity index 81% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/index.ts index 760a206dfa6ba..693d9578644ac 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/index.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats_grid/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { FieldsStats } from './fields_stats'; +export { FieldsStatsGrid } from './fields_stats_grid'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js index 56b81e36f1e92..a376e64b78216 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js @@ -228,7 +228,6 @@ export class FileDataVisualizerView extends Component { }; setOverrides = (overrides) => { - console.log('setOverrides', overrides); this.setState( { loading: true, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx index f9de03c119d28..12e60ea491421 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC } from 'react'; @@ -15,7 +14,6 @@ import { EuiPageBody, EuiPageContentHeader, EuiPanel, - EuiTabbedContent, EuiSpacer, EuiTitle, EuiFlexGroup, @@ -25,8 +23,7 @@ import { FindFileStructureResponse } from '../../../../../../common/types/file_d import { FileContents } from '../file_contents'; import { AnalysisSummary } from '../analysis_summary'; -// @ts-ignore -import { FieldsStats } from '../fields_stats'; +import { FieldsStatsGrid } from '../fields_stats_grid'; interface Props { data: string; @@ -45,16 +42,6 @@ export const ResultsView: FC = ({ showExplanationFlyout, disableButtons, }) => { - const tabs = [ - { - id: 'file-stats', - name: i18n.translate('xpack.ml.fileDatavisualizer.resultsView.fileStatsTabName', { - defaultMessage: 'File stats', - }), - content: , - }, - ]; - return ( @@ -103,7 +90,16 @@ export const ResultsView: FC = ({ - {}} /> + +

+ +

+
+ +
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/index_based/_index.scss index 53943f8ada6e8..95a523753dfca 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/_index.scss @@ -1 +1 @@ -@import 'components/field_data_card/index'; +@import 'components/field_data_row/index'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/common/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/index_based/common/index.ts index 50278c300d103..35bec2eb66379 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/common/index.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/common/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { FieldVisConfig } from './field_vis_config'; export { FieldHistogramRequestConfig, FieldRequestConfig } from './request'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/expanded_row.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx similarity index 75% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/expanded_row.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx index cb83d6db83ed3..7018f73ff6c32 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx @@ -6,22 +6,23 @@ import React from 'react'; -import { FieldVisConfig } from '../index_based/common'; +import { FieldVisConfig } from '../../../stats_table/types'; import { BooleanContent, DateContent, GeoPointContent, IpContent, KeywordContent, - NotInDocsContent, + NumberContent, OtherContent, TextContent, -} from '../index_based/components/field_data_card/content_types'; -import { NumberContent } from './components/field_data_expanded_row/number_content'; -import { ML_JOB_FIELD_TYPES } from '../../../../common/constants/field_types'; -import { LoadingIndicator } from '../index_based/components/field_data_card/loading_indicator'; +} from '../../../stats_table/components/field_data_expanded_row'; -export const DataVisualizerFieldExpandedRow = ({ item }: { item: FieldVisConfig }) => { +import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; +import { LoadingIndicator } from '../field_data_row/loading_indicator'; +import { NotInDocsContent } from '../field_data_row/content_types'; + +export const IndexBasedDataVisualizerExpandedRow = ({ item }: { item: FieldVisConfig }) => { const config = item; const { loading, type, existsInDocs, fieldName } = config; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/index.ts new file mode 100644 index 0000000000000..3b393d96c97e3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/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 { IndexBasedDataVisualizerExpandedRow } from './expanded_row'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_count_panel/field_count_panel.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_count_panel/field_count_panel.tsx index 61bf244fbbcdb..1996ca585147b 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_count_panel/field_count_panel.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_count_panel/field_count_panel.tsx @@ -4,19 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiNotificationBadge, EuiSwitch, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC } from 'react'; +import { + MetricFieldsCount, + TotalFieldsCount, +} from '../../../stats_table/components/field_count_stats'; +import type { + TotalFieldsCountProps, + MetricFieldsCountProps, +} from '../../../stats_table/components/field_count_stats'; -interface Props { - metricsStats?: { - visibleMetricFields: number; - totalMetricFields: number; - }; - fieldsCountStats?: { - visibleFieldsCount: number; - totalFieldsCount: number; - }; +interface Props extends TotalFieldsCountProps, MetricFieldsCountProps { showEmptyFields: boolean; toggleShowEmptyFields: () => void; } @@ -33,83 +33,8 @@ export const FieldCountPanel: FC = ({ style={{ marginLeft: 4 }} data-test-subj="mlDataVisualizerFieldCountPanel" > - {fieldsCountStats && ( - - - -
- -
-
-
- - - - {fieldsCountStats.visibleFieldsCount} - - - - - - - -
- )} - - {metricsStats && ( - - - -
- -
-
-
- - - {metricsStats.visibleMetricFields} - - - - - - - -
- )} - + + = 0.1) { - return `${value}%`; - } else { - return '< 0.1%'; - } -} - -export const BooleanContent: FC = ({ config }) => { - const { stats } = config; - if (stats === undefined) return null; - const { count, trueCount, falseCount } = stats; - if (count === undefined || trueCount === undefined || falseCount === undefined) return null; - - return ( -
- - - - - - - - - -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/geo_point_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/geo_point_content.tsx deleted file mode 100644 index 0c10cf1e6adcf..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/geo_point_content.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FC } from 'react'; - -import { FieldDataCardProps } from '../field_data_card'; -import { ExamplesList } from '../examples_list'; - -export const GeoPointContent: FC = ({ config }) => { - // TODO - adjust server-side query to get examples using: - - // GET /filebeat-apache-2019.01.30/_search - // { - // "size":10, - // "_source": false, - // "docvalue_fields": ["source.geo.location"], - // "query": { - // "bool":{ - // "must":[ - // { - // "exists":{ - // "field":"source.geo.location" - // } - // } - // ] - // } - // } - // } - - const { stats } = config; - if (stats?.examples === undefined) return null; - - return ( -
- -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/ip_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/ip_content.tsx deleted file mode 100644 index 4b54e86cdc495..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/ip_content.tsx +++ /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 React, { FC } from 'react'; -import { EuiSpacer } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -import { FieldDataCardProps } from '../field_data_card'; -import { TopValues } from '../top_values'; -import { ExpandedRowFieldHeader } from '../../../../stats_datagrid/components/expanded_row_field_header'; - -export const IpContent: FC = ({ config }) => { - const { stats, fieldFormat } = config; - if (stats === undefined) return null; - const { count, sampleCount, cardinality } = stats; - if (count === undefined || sampleCount === undefined || cardinality === undefined) return null; - - return ( -
- - - - - -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/keyword_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/keyword_content.tsx deleted file mode 100644 index 18c4fb190a125..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/keyword_content.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, { FC } from 'react'; -import { EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { FieldDataCardProps } from '../field_data_card'; -import { TopValues } from '../top_values'; -import { ExpandedRowFieldHeader } from '../../../../stats_datagrid/components/expanded_row_field_header'; - -export const KeywordContent: FC = ({ config }) => { - const { stats, fieldFormat } = config; - - return ( -
- - - - - -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx deleted file mode 100644 index 782880105da20..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx +++ /dev/null @@ -1,200 +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, { FC, Fragment, useEffect, useState } from 'react'; -import { - EuiButtonGroup, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiSpacer, - EuiText, -} from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; - -import { FieldDataCardProps } from '../field_data_card'; -import { DisplayValue } from '../../../../../components/display_value'; -import { kibanaFieldFormat } from '../../../../../formatters/kibana_field_format'; -import { numberAsOrdinal } from '../../../../../formatters/number_as_ordinal'; -import { roundToDecimalPlace } from '../../../../../formatters/round_to_decimal_place'; -import { - MetricDistributionChart, - MetricDistributionChartData, - buildChartDataFromStats, -} from '../metric_distribution_chart'; -import { TopValues } from '../top_values'; - -const DETAILS_MODE = { - DISTRIBUTION: 'distribution', - TOP_VALUES: 'top_values', -} as const; - -type DetailsModeType = typeof DETAILS_MODE[keyof typeof DETAILS_MODE]; - -const METRIC_DISTRIBUTION_CHART_WIDTH = 325; -const METRIC_DISTRIBUTION_CHART_HEIGHT = 210; -const DEFAULT_TOP_VALUES_THRESHOLD = 100; - -export const NumberContent: FC = ({ config }) => { - const { stats, fieldFormat } = config; - - useEffect(() => { - const chartData = buildChartDataFromStats(stats, METRIC_DISTRIBUTION_CHART_WIDTH); - setDistributionChartData(chartData); - }, []); - const [detailsMode, setDetailsMode] = useState( - stats?.cardinality ?? 0 <= DEFAULT_TOP_VALUES_THRESHOLD - ? DETAILS_MODE.TOP_VALUES - : DETAILS_MODE.DISTRIBUTION - ); - const defaultChartData: MetricDistributionChartData[] = []; - const [distributionChartData, setDistributionChartData] = useState(defaultChartData); - - if (stats === undefined) return null; - const { count, sampleCount, cardinality, min, median, max, distribution } = stats; - if (count === undefined || sampleCount === undefined) return null; - - const docsPercent = roundToDecimalPlace((count / sampleCount) * 100); - - const detailsOptions = [ - { - id: DETAILS_MODE.TOP_VALUES, - label: i18n.translate('xpack.ml.fieldDataCard.cardNumber.details.topValuesLabel', { - defaultMessage: 'Top values', - }), - }, - { - id: DETAILS_MODE.DISTRIBUTION, - label: i18n.translate('xpack.ml.fieldDataCard.cardNumber.details.distributionOfValuesLabel', { - defaultMessage: 'Distribution', - }), - }, - ]; - - return ( -
-
- - -   - - -
- -
- - -   - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setDetailsMode(optionId as DetailsModeType)} - legend={i18n.translate( - 'xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel', - { - defaultMessage: 'Select display option for metric details', - } - )} - data-test-subj="mlFieldDataCardDetailsSelect" - isFullWidth={true} - buttonSize="compressed" - /> - - {distribution && detailsMode === DETAILS_MODE.DISTRIBUTION && ( - - - - - - - - - - - - - - - )} - {detailsMode === DETAILS_MODE.TOP_VALUES && ( - - - - - - )} -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/other_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/other_content.tsx deleted file mode 100644 index 065d7d40c23e9..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/other_content.tsx +++ /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 React, { FC, Fragment } from 'react'; -import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -import { FieldDataCardProps } from '../field_data_card'; -import { roundToDecimalPlace } from '../../../../../formatters/round_to_decimal_place'; -import { ExamplesList } from '../examples_list'; - -export const OtherContent: FC = ({ config }) => { - const { stats, type, aggregatable } = config; - if (stats === undefined) return null; - - const { count, sampleCount, cardinality, examples } = stats; - if ( - count === undefined || - sampleCount === undefined || - cardinality === undefined || - examples === undefined - ) - return null; - - const docsPercent = roundToDecimalPlace((count / sampleCount) * 100); - - return ( -
-
- - - -
- {aggregatable === true && ( - - -
- - -   - - -
- - - -
- - -   - - -
-
- )} - - -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/text_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/text_content.tsx deleted file mode 100644 index d54d2237c6603..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/text_content.tsx +++ /dev/null @@ -1,62 +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, { FC, Fragment } from 'react'; -import { EuiCallOut, EuiSpacer } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; - -import { FieldDataCardProps } from '../field_data_card'; -import { ExamplesList } from '../examples_list'; - -export const TextContent: FC = ({ config }) => { - const { stats } = config; - if (stats === undefined) return null; - - const { examples } = stats; - if (examples === undefined) return null; - - const numExamples = examples.length; - - return ( -
- {numExamples > 0 && } - {numExamples === 0 && ( - - - - _source, - }} - /> - - - - copy_to, - sourceParam: _source, - includesParam: includes, - excludesParam: excludes, - }} - /> - - - )} -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/_index.scss similarity index 55% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_index.scss rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/_index.scss index e7c155d2554ba..38327dc51bd97 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/_index.scss @@ -1,2 +1 @@ -@import 'field_data_card'; @import 'top_values/top_values'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/document_count_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx similarity index 90% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/document_count_content.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx index e7b9604c1c06e..2df1b6799214d 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/document_count_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx @@ -6,11 +6,11 @@ import React, { FC } from 'react'; -import type { FieldDataCardProps } from '../field_data_card'; +import type { FieldDataRowProps } from '../../../../stats_table/types/field_data_row'; import { DocumentCountChart, DocumentCountChartPoint } from '../document_count_chart'; import { TotalCountHeader } from '../../total_count_header'; -export interface Props extends FieldDataCardProps { +export interface Props extends FieldDataRowProps { totalCount: number; } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/index.ts new file mode 100644 index 0000000000000..dd1f38b4a1349 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { DocumentCountContent } from './document_count_content'; +export { NotInDocsContent } from './not_in_docs_content'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/not_in_docs_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/not_in_docs_content.tsx similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/not_in_docs_content.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/not_in_docs_content.tsx diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx similarity index 98% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx index 6a02cb6acebd4..f0ac9b7e67d32 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx @@ -25,7 +25,6 @@ export interface DocumentCountChartPoint { interface Props { width?: number; - height?: number; chartPoints: DocumentCountChartPoint[]; timeRangeEarliest: number; timeRangeLatest: number; @@ -35,7 +34,6 @@ const SPEC_ID = 'document_count'; export const DocumentCountChart: FC = ({ width, - height, chartPoints, timeRangeEarliest, timeRangeLatest, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/index.ts rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/examples_list.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx similarity index 93% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/examples_list.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx index 5591e6f9b5417..1e8f7586258d5 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/examples_list.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx @@ -9,7 +9,7 @@ import React, { FC } from 'react'; import { EuiListGroup, EuiListGroupItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ExpandedRowFieldHeader } from '../../../../stats_datagrid/components/expanded_row_field_header'; +import { ExpandedRowFieldHeader } from '../../../../stats_table/components/expanded_row_field_header'; interface Props { examples: Array; } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/examples_list/index.ts rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/loading_indicator/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/loading_indicator/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/loading_indicator/index.ts rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/loading_indicator/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/loading_indicator/loading_indicator.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/loading_indicator/loading_indicator.tsx similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/loading_indicator/loading_indicator.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/loading_indicator/loading_indicator.tsx diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/top_values/_top_values.scss b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/_top_values.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/top_values/_top_values.scss rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/_top_values.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/top_values/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/top_values/index.ts rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/top_values/top_values.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/top_values.tsx similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/top_values/top_values.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/top_values.tsx diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx index af3c1a0e7c16c..8064e08c9f0f9 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx @@ -157,8 +157,6 @@ export const SearchPanel: FC = ({ setVisibleFieldTypes={setVisibleFieldTypes} visibleFieldTypes={visibleFieldTypes} /> - - ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx index 9819bf451e425..e5b243d524034 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx @@ -45,17 +45,23 @@ import { getToastNotifications } from '../../util/dependency_cache'; import { usePageUrlState, useUrlState } from '../../util/url_state'; import { ActionsPanel } from './components/actions_panel'; import { SearchPanel } from './components/search_panel'; -import { DocumentCountContent } from './components/field_data_card/content_types/document_count_content'; -import { DataVisualizerDataGrid } from '../stats_datagrid'; +import { DocumentCountContent } from './components/field_data_row/content_types/document_count_content'; +import { DataVisualizerTable, ItemIdToExpandedRowMap } from '../stats_table'; import { FieldCountPanel } from './components/field_count_panel'; import { ML_PAGES } from '../../../../common/constants/ml_url_generator'; import { DataLoader } from './data_loader'; -import type { FieldRequestConfig, FieldVisConfig } from './common'; +import type { FieldRequestConfig } from './common'; import type { DataVisualizerIndexBasedAppState } from '../../../../common/types/ml_url_generator'; import type { OverallStats } from '../../../../common/types/datavisualizer'; import { MlJobFieldType } from '../../../../common/types/field_types'; import { HelpMenu } from '../../components/help_menu'; import { useMlKibana } from '../../contexts/kibana'; +import { IndexBasedDataVisualizerExpandedRow } from './components/expanded_row'; +import { FieldVisConfig } from '../stats_table/types'; +import type { + MetricFieldsStats, + TotalFieldsStats, +} from '../stats_table/components/field_count_stats'; interface DataVisualizerPageState { overallStats: OverallStats; @@ -106,6 +112,19 @@ export const getDefaultDataVisualizerListState = (): Required { + const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName); + if (item !== undefined) { + m[fieldName] = ; + } + return m; + }, {} as ItemIdToExpandedRowMap); +} + export const Page: FC = () => { const mlContext = useMlContext(); const restorableDefaults = getDefaultDataVisualizerListState(); @@ -228,9 +247,7 @@ export const Page: FC = () => { const [documentCountStats, setDocumentCountStats] = useState(defaults.documentCountStats); const [metricConfigs, setMetricConfigs] = useState(defaults.metricConfigs); const [metricsLoaded, setMetricsLoaded] = useState(defaults.metricsLoaded); - const [metricsStats, setMetricsStats] = useState< - undefined | { visibleMetricFields: number; totalMetricFields: number } - >(); + const [metricsStats, setMetricsStats] = useState(); const [nonMetricConfigs, setNonMetricConfigs] = useState(defaults.nonMetricConfigs); const [nonMetricsLoaded, setNonMetricsLoaded] = useState(defaults.nonMetricsLoaded); @@ -537,8 +554,8 @@ export const Page: FC = () => { }); setMetricsStats({ - totalMetricFields: allMetricFields.length, - visibleMetricFields: metricFieldsToShow.length, + totalMetricFieldsCount: allMetricFields.length, + visibleMetricsCount: metricFieldsToShow.length, }); setMetricConfigs(configs); } @@ -642,7 +659,7 @@ export const Page: FC = () => { return combinedConfigs; }, [nonMetricConfigs, metricConfigs, visibleFieldTypes, visibleFieldNames]); - const fieldsCountStats = useMemo(() => { + const fieldsCountStats: TotalFieldsStats | undefined = useMemo(() => { let _visibleFieldsCount = 0; let _totalFieldsCount = 0; Object.keys(overallStats).forEach((key) => { @@ -736,10 +753,11 @@ export const Page: FC = () => { metricsStats={metricsStats} /> - items={configs} pageState={dataVisualizerListState} updatePageState={setDataVisualizerListState} + getItemIdToExpandedRowMap={getItemIdToExpandedRowMap} /> diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_field_data_row.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/_field_data_row.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss similarity index 87% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/_index.scss rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss index e9ecfc8b19100..6e7e66db9e03a 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss @@ -1,4 +1,5 @@ @import 'components/field_data_expanded_row/number_content'; +@import 'components/field_count_stats/index'; .mlDataVisualizerFieldExpandedRow { padding-left: $euiSize * 4; @@ -35,6 +36,7 @@ } } .mlDataVisualizerSummaryTable { + max-width: 350px; .euiTableRow > .euiTableRowCell { border-bottom: 0; } @@ -42,4 +44,7 @@ display: none; } } + .mlDataVisualizerSummaryTableWrapper { + max-width: 350px; + } } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/expanded_row_field_header/expanded_row_field_header.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/expanded_row_field_header/expanded_row_field_header.tsx similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/expanded_row_field_header/expanded_row_field_header.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/expanded_row_field_header/expanded_row_field_header.tsx diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/expanded_row_field_header/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/expanded_row_field_header/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/expanded_row_field_header/index.ts rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/expanded_row_field_header/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/_index.scss new file mode 100644 index 0000000000000..7154d0da2c09c --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/_index.scss @@ -0,0 +1,3 @@ +.mlDataVisualizerFieldCountContainer { + max-width: 300px; +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/index.ts new file mode 100644 index 0000000000000..15c9c92f51cfb --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/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. + */ + +export { TotalFieldsCount, TotalFieldsCountProps, TotalFieldsStats } from './total_fields_count'; +export { + MetricFieldsCount, + MetricFieldsCountProps, + MetricFieldsStats, +} from './metric_fields_count'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/metric_fields_count.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/metric_fields_count.tsx new file mode 100644 index 0000000000000..327a3e611296c --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/metric_fields_count.tsx @@ -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 { EuiFlexGroup, EuiFlexItem, EuiNotificationBadge, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { FC } from 'react'; + +export interface MetricFieldsStats { + visibleMetricsCount: number; + totalMetricFieldsCount: number; +} +export interface MetricFieldsCountProps { + metricsStats?: MetricFieldsStats; +} + +export const MetricFieldsCount: FC = ({ metricsStats }) => { + if ( + !metricsStats || + metricsStats.visibleMetricsCount === undefined || + metricsStats.totalMetricFieldsCount === undefined + ) + return null; + return ( + <> + {metricsStats && ( + + + +
+ +
+
+
+ + + {metricsStats.visibleMetricsCount} + + + + + + + +
+ )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/total_fields_count.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/total_fields_count.tsx new file mode 100644 index 0000000000000..c90770dbf8c57 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_count_stats/total_fields_count.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiNotificationBadge, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { FC } from 'react'; + +export interface TotalFieldsStats { + visibleFieldsCount: number; + totalFieldsCount: number; +} + +export interface TotalFieldsCountProps { + fieldsCountStats?: TotalFieldsStats; +} + +export const TotalFieldsCount: FC = ({ fieldsCountStats }) => { + if ( + !fieldsCountStats || + fieldsCountStats.visibleFieldsCount === undefined || + fieldsCountStats.totalFieldsCount === undefined + ) + return null; + + return ( + + + +
+ +
+
+
+ + + + {fieldsCountStats.visibleFieldsCount} + + + + + + + +
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_expanded_row/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_index.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_expanded_row/_index.scss rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_index.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_expanded_row/_number_content.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_number_content.scss similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_expanded_row/_number_content.scss rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_number_content.scss diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx new file mode 100644 index 0000000000000..a75920dd09b34 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, ReactNode, useMemo } from 'react'; +import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { Axis, BarSeries, Chart, Settings } from '@elastic/charts'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; +import { getTFPercentage } from '../../utils'; +import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; +import { useDataVizChartTheme } from '../../hooks'; +import { DocumentStatsTable } from './document_stats'; + +function getPercentLabel(value: number): string { + if (value === 0) { + return '0%'; + } + if (value >= 0.1) { + return `${roundToDecimalPlace(value)}%`; + } else { + return '< 0.1%'; + } +} + +function getFormattedValue(value: number, totalCount: number): string { + const percentage = (value / totalCount) * 100; + return `${value} (${getPercentLabel(percentage)})`; +} + +const BOOLEAN_DISTRIBUTION_CHART_HEIGHT = 100; + +export const BooleanContent: FC = ({ config }) => { + const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; + const formattedPercentages = useMemo(() => getTFPercentage(config), [config]); + const theme = useDataVizChartTheme(); + if (!formattedPercentages) return null; + + const { trueCount, falseCount, count } = formattedPercentages; + const summaryTableItems = [ + { + function: 'true', + display: ( + + ), + value: getFormattedValue(trueCount, count), + }, + { + function: 'false', + display: ( + + ), + value: getFormattedValue(falseCount, count), + }, + ]; + const summaryTableColumns = [ + { + name: '', + render: (summaryItem: { display: ReactNode }) => summaryItem.display, + width: '75px', + }, + { + field: 'value', + name: '', + render: (v: string) => {v}, + }, + ]; + + const summaryTableTitle = i18n.translate( + 'xpack.ml.fieldDataCardExpandedRow.booleanContent.summaryTableTitle', + { + defaultMessage: 'Summary', + } + ); + + return ( + + + + + {summaryTableTitle} + + + + + + + + + + + getFormattedValue(d, count)} + /> + + + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx similarity index 58% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx index 7651d20249c93..8d122df628381 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx @@ -5,14 +5,15 @@ */ import React, { FC, ReactNode } from 'react'; -import { EuiBasicTable } from '@elastic/eui'; +import { EuiBasicTable, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; // @ts-ignore import { formatDate } from '@elastic/eui/lib/services/format'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { FieldDataCardProps } from '../field_data_card'; -import { ExpandedRowFieldHeader } from '../../../../stats_datagrid/components/expanded_row_field_header'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; +import { DocumentStatsTable } from './document_stats'; const TIME_FORMAT = 'MMM D YYYY, HH:mm:ss.SSS'; interface SummaryTableItem { function: string; @@ -20,7 +21,7 @@ interface SummaryTableItem { value: number | string | undefined | null; } -export const DateContent: FC = ({ config }) => { +export const DateContent: FC = ({ config }) => { const { stats } = config; if (stats === undefined) return null; @@ -38,7 +39,7 @@ export const DateContent: FC = ({ config }) => { defaultMessage="earliest" /> ), - value: formatDate(earliest, TIME_FORMAT), + value: typeof earliest === 'string' ? earliest : formatDate(earliest, TIME_FORMAT), }, { function: 'latest', @@ -48,7 +49,7 @@ export const DateContent: FC = ({ config }) => { defaultMessage="latest" /> ), - value: formatDate(latest, TIME_FORMAT), + value: typeof latest === 'string' ? latest : formatDate(latest, TIME_FORMAT), }, ]; const summaryTableColumns = [ @@ -65,17 +66,20 @@ export const DateContent: FC = ({ config }) => { ]; return ( - <> - {summaryTableTitle} - - className={'mlDataVisualizerSummaryTable'} - data-test-subj={'mlDateSummaryTable'} - compressed - items={summaryTableItems} - columns={summaryTableColumns} - tableCaption={summaryTableTitle} - tableLayout="auto" - /> - + + + + {summaryTableTitle} + + className={'mlDataVisualizerSummaryTable'} + data-test-subj={'mlDateSummaryTable'} + compressed + items={summaryTableItems} + columns={summaryTableColumns} + tableCaption={summaryTableTitle} + tableLayout="auto" + /> + + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx new file mode 100644 index 0000000000000..177ac722166f7 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx @@ -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 { FormattedMessage } from '@kbn/i18n/react'; +import React, { FC, ReactNode } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiBasicTable, EuiFlexItem } from '@elastic/eui'; +import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; +import { FieldDataRowProps } from '../../types'; + +const metaTableColumns = [ + { + name: '', + render: (metaItem: { display: ReactNode }) => metaItem.display, + width: '75px', + }, + { + field: 'value', + name: '', + render: (v: string) => {v}, + }, +]; + +const metaTableTitle = i18n.translate( + 'xpack.ml.fieldDataCardExpandedRow.documentStatsTable.metaTableTitle', + { + defaultMessage: 'Documents stats', + } +); + +export const DocumentStatsTable: FC = ({ config }) => { + if ( + config?.stats === undefined || + config.stats.cardinality === undefined || + config.stats.count === undefined || + config.stats.sampleCount === undefined + ) + return null; + const { cardinality, count, sampleCount } = config.stats; + const metaTableItems = [ + { + function: 'count', + display: ( + + ), + value: count, + }, + { + function: 'percentage', + display: ( + + ), + value: `${(count / sampleCount) * 100}%`, + }, + { + function: 'distinctValues', + display: ( + + ), + value: cardinality, + }, + ]; + + return ( + + {metaTableTitle} + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.tsx new file mode 100644 index 0000000000000..993c7a94f5e06 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.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 React, { FC } from 'react'; + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; +import { DocumentStatsTable } from './document_stats'; +import { TopValues } from '../../../index_based/components/field_data_row/top_values'; + +export const GeoPointContent: FC = ({ config }) => { + const { stats } = config; + if (stats === undefined || (stats?.examples === undefined && stats?.topValues === undefined)) + return null; + + return ( + + + {Array.isArray(stats.examples) && ( + + + + )} + {Array.isArray(stats.topValues) && ( + + + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts similarity index 83% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/index.ts rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts index 230be246eb4eb..c6cd50f6bc2e9 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/index.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts @@ -6,11 +6,9 @@ export { BooleanContent } from './boolean_content'; export { DateContent } from './date_content'; -export { DocumentCountContent } from './document_count_content'; export { GeoPointContent } from './geo_point_content'; export { KeywordContent } from './keyword_content'; export { IpContent } from './ip_content'; -export { NotInDocsContent } from './not_in_docs_content'; export { NumberContent } from './number_content'; export { OtherContent } from './other_content'; export { TextContent } from './text_content'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx new file mode 100644 index 0000000000000..79492bb44a2dc --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { TopValues } from '../../../index_based/components/field_data_row/top_values'; +import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; +import { DocumentStatsTable } from './document_stats'; + +export const IpContent: FC = ({ config }) => { + const { stats } = config; + if (stats === undefined) return null; + const { count, sampleCount, cardinality } = stats; + if (count === undefined || sampleCount === undefined || cardinality === undefined) return null; + const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; + + return ( + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx new file mode 100644 index 0000000000000..634f5b55513a3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.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 React, { FC } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { TopValues } from '../../../index_based/components/field_data_row/top_values'; +import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; +import { DocumentStatsTable } from './document_stats'; + +export const KeywordContent: FC = ({ config }) => { + const { stats } = config; + const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; + + return ( + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_expanded_row/number_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx similarity index 89% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_expanded_row/number_content.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx index c3ba6d23f6baf..d05de26d3c5d4 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_expanded_row/number_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx @@ -9,17 +9,17 @@ import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui' import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; - -import { FieldDataCardProps } from '../../../index_based/components/field_data_card'; +import type { FieldDataRowProps } from '../../types/field_data_row'; import { kibanaFieldFormat } from '../../../../formatters/kibana_field_format'; import { numberAsOrdinal } from '../../../../formatters/number_as_ordinal'; import { MetricDistributionChart, MetricDistributionChartData, buildChartDataFromStats, -} from '../../../index_based/components/field_data_card/metric_distribution_chart'; -import { TopValues } from '../../../index_based/components/field_data_card/top_values'; +} from '../metric_distribution_chart'; +import { TopValues } from '../../../index_based/components/field_data_row/top_values'; import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; +import { DocumentStatsTable } from './document_stats'; const METRIC_DISTRIBUTION_CHART_WIDTH = 325; const METRIC_DISTRIBUTION_CHART_HEIGHT = 200; @@ -30,8 +30,8 @@ interface SummaryTableItem { value: number | string | undefined | null; } -export const NumberContent: FC = ({ config }) => { - const { stats, fieldFormat } = config; +export const NumberContent: FC = ({ config }) => { + const { stats } = config; useEffect(() => { const chartData = buildChartDataFromStats(stats, METRIC_DISTRIBUTION_CHART_WIDTH); @@ -43,6 +43,7 @@ export const NumberContent: FC = ({ config }) => { if (stats === undefined) return null; const { min, median, max, distribution } = stats; + const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; const summaryTableItems = [ { @@ -96,8 +97,9 @@ export const NumberContent: FC = ({ config }) => { } ); return ( - - + + + {summaryTableTitle} className={'mlDataVisualizerSummaryTable'} @@ -105,8 +107,10 @@ export const NumberContent: FC = ({ config }) => { items={summaryTableItems} columns={summaryTableColumns} tableCaption={summaryTableTitle} + data-test-subj={'mlNumberSummaryTable'} /> + {stats && ( diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx new file mode 100644 index 0000000000000..a6d7398990cd3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiFlexGroup } from '@elastic/eui'; +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; +import { DocumentStatsTable } from './document_stats'; + +export const OtherContent: FC = ({ config }) => { + const { stats } = config; + if (stats === undefined) return null; + return ( + + + {Array.isArray(stats.examples) && } + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx new file mode 100644 index 0000000000000..55639ecc5761f --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment } from 'react'; +import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import type { FieldDataRowProps } from '../../types/field_data_row'; +import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; + +export const TextContent: FC = ({ config }) => { + const { stats } = config; + if (stats === undefined) return null; + + const { examples } = stats; + if (examples === undefined) return null; + + const numExamples = examples.length; + + return ( + + + {numExamples > 0 && } + {numExamples === 0 && ( + + + + _source, + }} + /> + + + + copy_to, + sourceParam: _source, + includesParam: includes, + excludesParam: excludes, + }} + /> + + + )} + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/boolean_content_preview.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/boolean_content_preview.tsx new file mode 100644 index 0000000000000..e1a8a2f0dbeb6 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/boolean_content_preview.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { FC, useMemo } from 'react'; +import { EuiDataGridColumn } from '@elastic/eui'; +import { FieldDataRowProps } from '../../types'; +import { getTFPercentage } from '../../utils'; +import { ColumnChart } from '../../../../components/data_grid/column_chart'; +import { OrdinalChartData } from '../../../../components/data_grid/use_column_chart'; + +export const BooleanContentPreview: FC = ({ config }) => { + const chartData = useMemo(() => { + const results = getTFPercentage(config); + if (results) { + const data = [ + { key: 'true', key_as_string: 'true', doc_count: results.trueCount }, + { key: 'false', key_as_string: 'false', doc_count: results.falseCount }, + ]; + return { id: config.fieldName, cardinality: 2, data, type: 'boolean' } as OrdinalChartData; + } + }, [config]); + if (!chartData || config.fieldName === undefined) return null; + + const columnType: EuiDataGridColumn = { + id: config.fieldName, + schema: undefined, + }; + const dataTestSubj = `mlDataGridChart-${config.fieldName}`; + + return ( + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/distinct_values.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/distinct_values.tsx similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/distinct_values.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/distinct_values.tsx diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/document_stats.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx similarity index 86% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/document_stats.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx index 9421b7d9f51e7..9c89d74fa751b 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/document_stats.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx @@ -7,10 +7,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; import React from 'react'; -import { FieldDataCardProps } from '../../../index_based/components/field_data_card'; +import type { FieldDataRowProps } from '../../types/field_data_row'; import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; -export const DocumentStat = ({ config }: FieldDataCardProps) => { +export const DocumentStat = ({ config }: FieldDataRowProps) => { const { stats } = config; if (stats === undefined) return null; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/index.ts similarity index 78% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/index.ts rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/index.ts index 35a785e3cba67..2f1a958e657fd 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/index.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { DataVisualizerDataGrid } from './stats_datagrid'; +export { BooleanContentPreview } from './boolean_content_preview'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/number_content_preview.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx similarity index 91% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/number_content_preview.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx index 13070aeac6a4e..3a84ae644cb4e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/number_content_preview.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx @@ -7,18 +7,22 @@ import React, { FC, useEffect, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import classNames from 'classnames'; -import { FieldDataCardProps } from '../../../index_based/components/field_data_card'; import { MetricDistributionChart, MetricDistributionChartData, buildChartDataFromStats, -} from '../../../index_based/components/field_data_card/metric_distribution_chart'; +} from '../metric_distribution_chart'; import { formatSingleValue } from '../../../../formatters/format_value'; +import { FieldVisConfig } from '../../types'; const METRIC_DISTRIBUTION_CHART_WIDTH = 150; const METRIC_DISTRIBUTION_CHART_HEIGHT = 80; -export const NumberContentPreview: FC = ({ config }) => { +export interface NumberContentPreviewProps { + config: FieldVisConfig; +} + +export const IndexBasedNumberContentPreview: FC = ({ config }) => { const { stats, fieldFormat, fieldName } = config; const defaultChartData: MetricDistributionChartData[] = []; const [distributionChartData, setDistributionChartData] = useState(defaultChartData); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/top_values_preview.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/top_values_preview.tsx similarity index 89% rename from x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/top_values_preview.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/top_values_preview.tsx index 52607ee71f25b..3ae9147e475b1 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_datagrid/components/field_data_row/top_values_preview.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/top_values_preview.tsx @@ -6,12 +6,12 @@ import React, { FC } from 'react'; import { EuiDataGridColumn } from '@elastic/eui'; -import { FieldDataCardProps } from '../../../index_based/components/field_data_card'; +import type { FieldDataRowProps } from '../../types/field_data_row'; import { ColumnChart } from '../../../../components/data_grid/column_chart'; import { ChartData } from '../../../../components/data_grid'; import { OrdinalDataItem } from '../../../../components/data_grid/use_column_chart'; -export const TopValuesPreview: FC = ({ config }) => { +export const TopValuesPreview: FC = ({ config }) => { const { stats } = config; if (stats === undefined) return null; const { topValues, cardinality } = stats; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/metric_distribution_chart/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/index.ts rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/metric_distribution_chart/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/metric_distribution_chart/metric_distribution_chart.tsx similarity index 60% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/metric_distribution_chart/metric_distribution_chart.tsx index 1abc497438079..786ebd9866cca 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/metric_distribution_chart/metric_distribution_chart.tsx @@ -19,13 +19,10 @@ import { TooltipValueFormatter, } from '@elastic/charts'; -import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; -import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; - import { MetricDistributionChartTooltipHeader } from './metric_distribution_chart_tooltip_header'; -import { useUiSettings } from '../../../../../contexts/kibana/use_ui_settings_context'; -import { kibanaFieldFormat } from '../../../../../formatters/kibana_field_format'; -import type { ChartTooltipValue } from '../../../../../components/chart_tooltip/chart_tooltip_service'; +import { kibanaFieldFormat } from '../../../../formatters/kibana_field_format'; +import type { ChartTooltipValue } from '../../../../components/chart_tooltip/chart_tooltip_service'; +import { useDataVizChartTheme } from '../../hooks'; export interface MetricDistributionChartData { x: number; @@ -59,9 +56,7 @@ export const MetricDistributionChart: FC = ({ defaultMessage: 'distribution', }); - const IS_DARK_THEME = useUiSettings().get('theme:darkMode'); - const themeName = IS_DARK_THEME ? darkTheme : lightTheme; - const AREA_SERIES_COLOR = themeName.euiColorVis0; + const theme = useDataVizChartTheme(); const headerFormatter: TooltipValueFormatter = (tooltipData: ChartTooltipValue) => { const xValue = tooltipData.value; @@ -81,47 +76,7 @@ export const MetricDistributionChart: FC = ({ return (
- + ) => void; -} +const FIELD_NAME = 'fieldName'; export type ItemIdToExpandedRowMap = Record; -function getItemIdToExpandedRowMap( - itemIds: string[], - items: FieldVisConfig[] -): ItemIdToExpandedRowMap { - return itemIds.reduce((m: ItemIdToExpandedRowMap, fieldName: string) => { - const item = items.find((fieldVisConfig) => fieldVisConfig[FIELD_NAME] === fieldName); - if (item !== undefined) { - m[fieldName] = ; - } - return m; - }, {} as ItemIdToExpandedRowMap); +type DataVisualizerTableItem = FieldVisConfig | FileBasedFieldVisConfig; +interface DataVisualizerTableProps { + items: T[]; + pageState: DataVisualizerIndexBasedAppState | DataVisualizerFileBasedAppState; + updatePageState: ( + update: Partial + ) => void; + getItemIdToExpandedRowMap: (itemIds: string[], items: T[]) => ItemIdToExpandedRowMap; } -export const DataVisualizerDataGrid = ({ +export const DataVisualizerTable = ({ items, pageState, updatePageState, -}: DataVisualizerDataGrid) => { + getItemIdToExpandedRowMap, +}: DataVisualizerTableProps) => { const [expandedRowItemIds, setExpandedRowItemIds] = useState([]); const [expandAll, toggleExpandAll] = useState(false); - const { onTableChange, pagination, sorting } = useTableSettings( + const { onTableChange, pagination, sorting } = useTableSettings( items, pageState, updatePageState ); - const showDistributions: boolean = pageState.showDistributions ?? true; + const showDistributions: boolean = + ('showDistributions' in pageState && pageState.showDistributions) ?? true; const toggleShowDistribution = () => { updatePageState({ ...pageState, @@ -73,7 +76,7 @@ export const DataVisualizerDataGrid = ({ }); }; - function toggleDetails(item: FieldVisConfig) { + function toggleDetails(item: DataVisualizerTableItem) { if (item.fieldName === undefined) return; const index = expandedRowItemIds.indexOf(item.fieldName); if (index !== -1) { @@ -87,7 +90,7 @@ export const DataVisualizerDataGrid = ({ } const columns = useMemo(() => { - const expanderColumn: EuiTableComputedColumnType = { + const expanderColumn: EuiTableComputedColumnType = { name: ( { + render: (item: DataVisualizerTableItem) => { if (item.fieldName === undefined) return null; const direction = expandedRowItemIds.includes(item.fieldName) ? 'arrowUp' : 'arrowDown'; return ( @@ -167,8 +170,10 @@ export const DataVisualizerDataGrid = ({ name: i18n.translate('xpack.ml.datavisualizer.dataGrid.documentsCountColumnName', { defaultMessage: 'Documents (%)', }), - render: (value: number | undefined, item: FieldVisConfig) => , - sortable: (item: FieldVisConfig) => item?.stats?.count, + render: (value: number | undefined, item: DataVisualizerTableItem) => ( + + ), + sortable: (item: DataVisualizerTableItem) => item?.stats?.count, align: LEFT_ALIGNMENT as HorizontalAlignment, 'data-test-subj': 'mlDataVisualizerTableColumnDocumentsCount', }, @@ -203,15 +208,27 @@ export const DataVisualizerDataGrid = ({ />
), - render: (item: FieldVisConfig) => { + render: (item: DataVisualizerTableItem) => { if (item === undefined || showDistributions === false) return null; - if (item.type === 'keyword' && item.stats?.topValues !== undefined) { + if ( + (item.type === ML_JOB_FIELD_TYPES.KEYWORD || item.type === ML_JOB_FIELD_TYPES.IP) && + item.stats?.topValues !== undefined + ) { return ; } - if (item.type === 'number' && item.stats?.distribution !== undefined) { - return ; + if (item.type === ML_JOB_FIELD_TYPES.NUMBER) { + if (isIndexBasedFieldVisConfig(item) && item.stats?.distribution !== undefined) { + return ; + } else { + return ; + } } + + if (item.type === ML_JOB_FIELD_TYPES.BOOLEAN) { + return ; + } + return null; }, align: LEFT_ALIGNMENT as HorizontalAlignment, @@ -230,7 +247,7 @@ export const DataVisualizerDataGrid = ({ return ( - + className={'mlDataVisualizer'} items={items} itemId={FIELD_NAME} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/hooks/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/hooks/index.ts new file mode 100644 index 0000000000000..787bd71fce481 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/hooks/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 { useDataVizChartTheme } from './use_data_viz_chart_theme'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/hooks/use_data_viz_chart_theme.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/hooks/use_data_viz_chart_theme.ts new file mode 100644 index 0000000000000..14e83da0546a6 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/hooks/use_data_viz_chart_theme.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import type { PartialTheme } from '@elastic/charts'; +import { useMemo } from 'react'; +import { useCurrentEuiTheme } from '../../../components/color_range_legend'; +export const useDataVizChartTheme = (): PartialTheme => { + const { euiTheme } = useCurrentEuiTheme(); + const chartTheme = useMemo(() => { + const AREA_SERIES_COLOR = euiTheme.euiColorVis0; + return { + axes: { + tickLabel: { + fontSize: parseInt(euiTheme.euiFontSizeXS, 10), + fontFamily: euiTheme.euiFontFamily, + fontStyle: 'italic', + }, + }, + background: { color: 'transparent' }, + chartMargins: { + left: 0, + right: 0, + top: 0, + bottom: 0, + }, + chartPaddings: { + left: 0, + right: 0, + top: 4, + bottom: 0, + }, + scales: { barsPadding: 0.1 }, + colors: { + vizColors: [AREA_SERIES_COLOR], + }, + areaSeriesStyle: { + line: { + strokeWidth: 1, + visible: true, + }, + point: { + visible: false, + radius: 0, + opacity: 0, + }, + area: { visible: true, opacity: 1 }, + }, + }; + }, [euiTheme]); + return chartTheme; +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/index.ts new file mode 100644 index 0000000000000..f903113a39ca6 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/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 { DataVisualizerTable, ItemIdToExpandedRowMap } from './data_visualizer_stats_table'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_data_row.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_data_row.ts new file mode 100644 index 0000000000000..4f52534d9d75b --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_data_row.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 type { FieldVisConfig, FileBasedFieldVisConfig } from './field_vis_config'; + +export interface FieldDataRowProps { + config: FieldVisConfig | FileBasedFieldVisConfig; +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/common/field_vis_config.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts similarity index 69% rename from x-pack/plugins/ml/public/application/datavisualizer/index_based/common/field_vis_config.ts rename to x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts index 4783107742799..a35765dd9b15e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/common/field_vis_config.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts @@ -50,7 +50,7 @@ export interface FieldVisStats { max?: number; median?: number; min?: number; - topValues?: Array<{ key: number; doc_count: number }>; + topValues?: Array<{ key: number | string; doc_count: number }>; topValuesSampleSize?: number; topValuesSamplerShardSize?: number; examples?: Array; @@ -70,3 +70,28 @@ export interface FieldVisConfig { fieldFormat?: any; isUnsupportedType?: boolean; } + +export interface FileBasedFieldVisConfig { + type: MlJobFieldType; + fieldName?: string; + stats?: FieldVisStats; + format?: string; +} + +export interface FileBasedUnknownFieldVisConfig { + fieldName: string; + type: 'text' | 'unknown'; + stats: { mean: number; count: number; sampleCount: number; cardinality: number }; +} + +export function isFileBasedFieldVisConfig( + field: FieldVisConfig | FileBasedFieldVisConfig +): field is FileBasedFieldVisConfig { + return !field.hasOwnProperty('existsInDocs'); +} + +export function isIndexBasedFieldVisConfig( + field: FieldVisConfig | FileBasedFieldVisConfig +): field is FieldVisConfig { + return field.hasOwnProperty('existsInDocs'); +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/index.ts new file mode 100644 index 0000000000000..439d4f037ca15 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { FieldDataRowProps } from './field_data_row'; +export { + FieldVisConfig, + FileBasedFieldVisConfig, + FieldVisStats, + MetricFieldVisStats, + isFileBasedFieldVisConfig, + isIndexBasedFieldVisConfig, +} from './field_vis_config'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/utils.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/utils.ts new file mode 100644 index 0000000000000..ead30b9498a62 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/utils.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. + */ + +import { FileBasedFieldVisConfig } from './types'; + +export const getTFPercentage = (config: FileBasedFieldVisConfig) => { + const { stats } = config; + if (stats === undefined) return null; + const { count } = stats; + // use stats from index based config + let { trueCount, falseCount } = stats; + + // use stats from file based find structure results + if (stats.trueCount === undefined || stats.falseCount === undefined) { + if (config?.stats?.topValues) { + config.stats.topValues.forEach((doc) => { + if (doc.doc_count !== undefined) { + if (doc.key.toString().toLowerCase() === 'false') { + falseCount = doc.doc_count; + } + if (doc.key.toString().toLowerCase() === 'true') { + trueCount = doc.doc_count; + } + } + }); + } + } + if (count === undefined || trueCount === undefined || falseCount === undefined) return null; + return { + count, + trueCount, + falseCount, + }; +}; diff --git a/x-pack/plugins/ml/public/application/formatters/round_to_decimal_place.ts b/x-pack/plugins/ml/public/application/formatters/round_to_decimal_place.ts index 5a030d7619e98..88a82da5ed9d0 100644 --- a/x-pack/plugins/ml/public/application/formatters/round_to_decimal_place.ts +++ b/x-pack/plugins/ml/public/application/formatters/round_to_decimal_place.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export function roundToDecimalPlace(num: number, dp: number = 2): number | string { +export function roundToDecimalPlace(num?: number, dp: number = 2): number | string { + if (num === undefined) return ''; if (num % 1 === 0) { // no decimal place return num; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0c432a039b112..65298463c9808 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13126,18 +13126,6 @@ "xpack.ml.fieldDataCard.cardDate.summaryTableTitle": "まとめ", "xpack.ml.fieldDataCard.cardIp.topValuesLabel": "トップの値", "xpack.ml.fieldDataCard.cardKeyword.topValuesLabel": "トップの値", - "xpack.ml.fieldDataCard.cardNumber.details.distributionOfValuesLabel": "分布", - "xpack.ml.fieldDataCard.cardNumber.details.topValuesLabel": "トップの値", - "xpack.ml.fieldDataCard.cardNumber.displayingPercentilesLabel": "{minPercent} - {maxPercent} パーセンタイルを表示中", - "xpack.ml.fieldDataCard.cardNumber.distinctCountDescription": "{cardinality} 個の特徴的な {cardinality, plural, other {値}}", - "xpack.ml.fieldDataCard.cardNumber.documentsCountDescription": "{count, plural, other {# 個のドキュメント}} ({docsPercent}%)", - "xpack.ml.fieldDataCard.cardNumber.maxLabel": "最高", - "xpack.ml.fieldDataCard.cardNumber.medianLabel": "中間", - "xpack.ml.fieldDataCard.cardNumber.minLabel": "分", - "xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel": "メトリック詳細の表示オプションを選択してください", - "xpack.ml.fieldDataCard.cardOther.cardTypeLabel": "{cardType} タイプ", - "xpack.ml.fieldDataCard.cardOther.distinctCountDescription": "{cardinality} 個の特徴的な {cardinality, plural, other {値}}", - "xpack.ml.fieldDataCard.cardOther.documentsCountDescription": "{count, plural, other {# 個のドキュメント}} ({docsPercent}%)", "xpack.ml.fieldDataCard.cardText.fieldMayBePopulatedDescription": "たとえば、ドキュメントマッピングで {copyToParam} パラメーターを使ったり、{includesParam} と {excludesParam} パラメーターを使用してインデックスした後に {sourceParam} フィールドから切り取ったりして入力される場合があります。", "xpack.ml.fieldDataCard.cardText.fieldNotPresentDescription": "このフィールドはクエリが実行されたドキュメントの {sourceParam} フィールドにありませんでした。", "xpack.ml.fieldDataCard.cardText.noExamplesForFieldsTitle": "このフィールドの例が取得されませんでした", @@ -13221,13 +13209,9 @@ "xpack.ml.fileDatavisualizer.explanationFlyout.closeButton": "閉じる", "xpack.ml.fileDatavisualizer.explanationFlyout.content": "分析結果を生成した論理ステップ。", "xpack.ml.fileDatavisualizer.explanationFlyout.title": "分析説明", - "xpack.ml.fileDatavisualizer.fieldStatsCard.distinctCountDescription": "{fieldCardinality} 個の特徴的な {fieldCardinality, plural, other {値}}", - "xpack.ml.fileDatavisualizer.fieldStatsCard.documentsCountDescription": "{fieldCount, plural, other {# 個のドキュメント}} ({fieldPercent}%)", "xpack.ml.fileDatavisualizer.fieldStatsCard.maxTitle": "最高", "xpack.ml.fileDatavisualizer.fieldStatsCard.medianTitle": "中間", "xpack.ml.fileDatavisualizer.fieldStatsCard.minTitle": "分", - "xpack.ml.fileDatavisualizer.fieldStatsCard.noFieldInformationAvailableDescription": "フィールド情報がありません", - "xpack.ml.fileDatavisualizer.fieldStatsCard.topStatsValuesDescription": "トップの値", "xpack.ml.fileDatavisualizer.fileBeatConfig.paths": "ファイルのパスをここに追加してください", "xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.closeButton": "閉じる", "xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.copyButton": "クリップボードにコピー", @@ -13314,7 +13298,6 @@ "xpack.ml.fileDatavisualizer.resultsLinks.openInDataVisualizerTitle": "データビジュアライザーを開く", "xpack.ml.fileDatavisualizer.resultsLinks.viewIndexInDiscoverTitle": "インデックスをディスカバリで表示", "xpack.ml.fileDatavisualizer.resultsView.analysisExplanationButtonLabel": "分析説明", - "xpack.ml.fileDatavisualizer.resultsView.fileStatsTabName": "ファイル統計", "xpack.ml.fileDatavisualizer.resultsView.overrideSettingsButtonLabel": "上書き設定", "xpack.ml.fileDatavisualizer.simpleImportSettings.createIndexPatternLabel": "インデックスパターンを作成", "xpack.ml.fileDatavisualizer.simpleImportSettings.indexNameAriaLabel": "インデックス名、必須フィールド", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 627e7e18bf617..7befbcf34e4d8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13157,18 +13157,6 @@ "xpack.ml.fieldDataCard.cardDate.summaryTableTitle": "摘要", "xpack.ml.fieldDataCard.cardIp.topValuesLabel": "排名最前值", "xpack.ml.fieldDataCard.cardKeyword.topValuesLabel": "排名最前值", - "xpack.ml.fieldDataCard.cardNumber.details.distributionOfValuesLabel": "分布", - "xpack.ml.fieldDataCard.cardNumber.details.topValuesLabel": "排名最前值", - "xpack.ml.fieldDataCard.cardNumber.displayingPercentilesLabel": "显示 {minPercent} - {maxPercent} 百分位数", - "xpack.ml.fieldDataCard.cardNumber.distinctCountDescription": "{cardinality} 个不同的 {cardinality, plural, other {值}}", - "xpack.ml.fieldDataCard.cardNumber.documentsCountDescription": "{count, plural, other {# 个文档}} ({docsPercent}%)", - "xpack.ml.fieldDataCard.cardNumber.maxLabel": "最大值", - "xpack.ml.fieldDataCard.cardNumber.medianLabel": "中值", - "xpack.ml.fieldDataCard.cardNumber.minLabel": "最小值", - "xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel": "选择指标详情的显示选项", - "xpack.ml.fieldDataCard.cardOther.cardTypeLabel": "{cardType} 类型", - "xpack.ml.fieldDataCard.cardOther.distinctCountDescription": "{cardinality} 个不同的 {cardinality, plural, other {值}}", - "xpack.ml.fieldDataCard.cardOther.documentsCountDescription": "{count, plural, other {# 个文档}} ({docsPercent}%)", "xpack.ml.fieldDataCard.cardText.fieldMayBePopulatedDescription": "例如,可以使用文档映射中的 {copyToParam} 参数进行填充,也可以在索引后通过使用 {includesParam} 和 {excludesParam} 参数从 {sourceParam} 字段中修剪。", "xpack.ml.fieldDataCard.cardText.fieldNotPresentDescription": "查询的文档的 {sourceParam} 字段中不存在此字段。", "xpack.ml.fieldDataCard.cardText.noExamplesForFieldsTitle": "没有获取此字段的示例", @@ -13252,13 +13240,9 @@ "xpack.ml.fileDatavisualizer.explanationFlyout.closeButton": "关闭", "xpack.ml.fileDatavisualizer.explanationFlyout.content": "产生分析结果的逻辑步骤。", "xpack.ml.fileDatavisualizer.explanationFlyout.title": "分析说明", - "xpack.ml.fileDatavisualizer.fieldStatsCard.distinctCountDescription": "{fieldCardinality} 个不同的{fieldCardinality, plural, other {值}}", - "xpack.ml.fileDatavisualizer.fieldStatsCard.documentsCountDescription": "{fieldCount, plural, other {# 个文档}} ({fieldPercent}%)", "xpack.ml.fileDatavisualizer.fieldStatsCard.maxTitle": "最大值", "xpack.ml.fileDatavisualizer.fieldStatsCard.medianTitle": "中值", "xpack.ml.fileDatavisualizer.fieldStatsCard.minTitle": "最小值", - "xpack.ml.fileDatavisualizer.fieldStatsCard.noFieldInformationAvailableDescription": "没有可用的字段信息", - "xpack.ml.fileDatavisualizer.fieldStatsCard.topStatsValuesDescription": "排在前面的值", "xpack.ml.fileDatavisualizer.fileBeatConfig.paths": "在此处将路径添加您的文件中", "xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.closeButton": "关闭", "xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.copyButton": "复制到剪贴板", @@ -13346,7 +13330,6 @@ "xpack.ml.fileDatavisualizer.resultsLinks.openInDataVisualizerTitle": "在数据可视化工具中打开", "xpack.ml.fileDatavisualizer.resultsLinks.viewIndexInDiscoverTitle": "在 Discover 中查看索引", "xpack.ml.fileDatavisualizer.resultsView.analysisExplanationButtonLabel": "分析说明", - "xpack.ml.fileDatavisualizer.resultsView.fileStatsTabName": "文件统计", "xpack.ml.fileDatavisualizer.resultsView.overrideSettingsButtonLabel": "替代设置", "xpack.ml.fileDatavisualizer.simpleImportSettings.createIndexPatternLabel": "创建索引模式", "xpack.ml.fileDatavisualizer.simpleImportSettings.indexNameAriaLabel": "索引名称,必填字段", diff --git a/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts index fc0c339ca2693..531eba54f931d 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts @@ -7,6 +7,7 @@ import path from 'path'; import { FtrProviderContext } from '../../../ftr_provider_context'; +import { ML_JOB_FIELD_TYPES } from '../../../../../plugins/ml/common/constants/field_types'; export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); @@ -17,11 +18,98 @@ export default function ({ getService }: FtrProviderContext) { filePath: path.join(__dirname, 'files_to_import', 'artificial_server_log'), indexName: 'user-import_1', createIndexPattern: false, + fieldTypeFilters: [ML_JOB_FIELD_TYPES.NUMBER, ML_JOB_FIELD_TYPES.DATE], + fieldNameFilters: ['clientip'], expected: { results: { title: 'artificial_server_log', numberOfFields: 4, }, + metricFields: [ + { + fieldName: 'bytes', + type: ML_JOB_FIELD_TYPES.NUMBER, + docCountFormatted: '19 (100%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 8, + }, + { + fieldName: 'httpversion', + type: ML_JOB_FIELD_TYPES.NUMBER, + docCountFormatted: '19 (100%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 1, + }, + { + fieldName: 'response', + type: ML_JOB_FIELD_TYPES.NUMBER, + docCountFormatted: '19 (100%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 3, + }, + ], + nonMetricFields: [ + { + fieldName: 'timestamp', + type: ML_JOB_FIELD_TYPES.DATE, + docCountFormatted: '19 (100%)', + exampleCount: 10, + }, + { + fieldName: 'agent', + type: ML_JOB_FIELD_TYPES.KEYWORD, + exampleCount: 8, + docCountFormatted: '19 (100%)', + }, + { + fieldName: 'auth', + type: ML_JOB_FIELD_TYPES.KEYWORD, + exampleCount: 1, + docCountFormatted: '19 (100%)', + }, + { + fieldName: 'ident', + type: ML_JOB_FIELD_TYPES.KEYWORD, + exampleCount: 1, + docCountFormatted: '19 (100%)', + }, + { + fieldName: 'verb', + type: ML_JOB_FIELD_TYPES.KEYWORD, + exampleCount: 1, + docCountFormatted: '19 (100%)', + }, + { + fieldName: 'request', + type: ML_JOB_FIELD_TYPES.KEYWORD, + exampleCount: 2, + docCountFormatted: '19 (100%)', + }, + { + fieldName: 'referrer', + type: ML_JOB_FIELD_TYPES.KEYWORD, + exampleCount: 1, + docCountFormatted: '19 (100%)', + }, + { + fieldName: 'clientip', + type: ML_JOB_FIELD_TYPES.IP, + exampleCount: 7, + docCountFormatted: '19 (100%)', + }, + { + fieldName: 'message', + type: ML_JOB_FIELD_TYPES.TEXT, + exampleCount: 10, + docCountFormatted: '19 (100%)', + }, + ], + visibleMetricFieldsCount: 3, + totalMetricFieldsCount: 3, + populatedFieldsCount: 12, + totalFieldsCount: 12, + fieldTypeFiltersResultCount: 4, + fieldNameFiltersResultCount: 1, }, }, ]; @@ -63,8 +151,65 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataVisualizerFileBased.assertFileContentPanelExists(); await ml.dataVisualizerFileBased.assertSummaryPanelExists(); await ml.dataVisualizerFileBased.assertFileStatsPanelExists(); - await ml.dataVisualizerFileBased.assertNumberOfFieldCards( - testData.expected.results.numberOfFields + + await ml.testExecution.logTestStep( + `displays elements in the data visualizer table correctly` + ); + await ml.dataVisualizerIndexBased.assertDataVisualizerTableExist(); + + await ml.dataVisualizerIndexBased.assertVisibleMetricFieldsCount( + testData.expected.visibleMetricFieldsCount + ); + await ml.dataVisualizerIndexBased.assertTotalMetricFieldsCount( + testData.expected.totalMetricFieldsCount + ); + await ml.dataVisualizerIndexBased.assertVisibleFieldsCount( + testData.expected.totalFieldsCount + ); + await ml.dataVisualizerIndexBased.assertTotalFieldsCount( + testData.expected.totalFieldsCount + ); + + await ml.testExecution.logTestStep( + 'displays details for metric fields and non-metric fields correctly' + ); + await ml.dataVisualizerTable.ensureNumRowsPerPage(25); + + for (const fieldRow of testData.expected.metricFields) { + await ml.dataVisualizerTable.assertNumberFieldContents( + fieldRow.fieldName, + fieldRow.docCountFormatted, + fieldRow.topValuesCount, + false + ); + } + for (const fieldRow of testData.expected.nonMetricFields!) { + await ml.dataVisualizerTable.assertNonMetricFieldContents( + fieldRow.type, + fieldRow.fieldName!, + fieldRow.docCountFormatted, + fieldRow.exampleCount + ); + } + + await ml.testExecution.logTestStep('sets and resets field type filter correctly'); + await ml.dataVisualizerTable.setFieldTypeFilter( + testData.fieldTypeFilters, + testData.expected.fieldTypeFiltersResultCount + ); + await ml.dataVisualizerTable.removeFieldTypeFilter( + testData.fieldTypeFilters, + testData.expected.totalFieldsCount + ); + + await ml.testExecution.logTestStep('sets and resets field name filter correctly'); + await ml.dataVisualizerTable.setFieldNameFilter( + testData.fieldNameFilters, + testData.expected.fieldNameFiltersResultCount + ); + await ml.dataVisualizerTable.removeFieldNameFilter( + testData.fieldNameFilters, + testData.expected.totalFieldsCount ); await ml.testExecution.logTestStep('loads the import settings page'); diff --git a/x-pack/test/functional/apps/ml/data_visualizer/files_to_import/artificial_server_log b/x-pack/test/functional/apps/ml/data_visualizer/files_to_import/artificial_server_log index 3571d3c9b5e42..d9be69996b86a 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/files_to_import/artificial_server_log +++ b/x-pack/test/functional/apps/ml/data_visualizer/files_to_import/artificial_server_log @@ -1,19 +1,20 @@ -2018-01-06 16:56:14.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.0 -2018-01-06 16:56:15.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.1 -2018-01-06 16:56:16.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.2 -2018-01-06 16:56:17.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.3 -2018-01-06 16:56:18.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.0 -2018-01-06 16:56:19.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.2 -2018-01-06 16:56:20.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.3 -2018-01-06 16:56:21.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.4 -2018-01-06 16:56:22.295748 WARN host:'Server A' Disk watermark 80% -2018-01-06 17:16:23.295748 WARN host:'Server A' Disk watermark 90% -2018-01-06 17:36:10.295748 ERROR host:'Server A' Main process crashed -2018-01-06 17:36:14.295748 INFO host:'Server A' Connection from ip 123.456.789.0 closed -2018-01-06 17:36:15.295748 INFO host:'Server A' Connection from ip 123.456.789.1 closed -2018-01-06 17:36:16.295748 INFO host:'Server A' Connection from ip 123.456.789.2 closed -2018-01-06 17:36:17.295748 INFO host:'Server A' Connection from ip 123.456.789.3 closed -2018-01-06 17:46:11.295748 INFO host:'Server B' Some special characters °!"§$%&/()=?`'^²³{[]}\+*~#'-_.:,;µ|<>äöüß -2018-01-06 17:46:12.295748 INFO host:'Server B' Shutting down - - +93.180.71.3 - - [17/May/2015:08:05:32 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)" +93.180.71.3 - - [17/May/2015:08:05:23 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)" +80.91.33.133 - - [17/May/2015:08:05:24 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)" +217.168.17.5 - - [17/May/2015:08:05:34 +0000] "GET /downloads/product_1 HTTP/1.1" 200 490 "-" "Debian APT-HTTP/1.3 (0.8.10.3)" +217.168.17.5 - - [17/May/2015:08:05:09 +0000] "GET /downloads/product_2 HTTP/1.1" 200 490 "-" "Debian APT-HTTP/1.3 (0.8.10.3)" +93.180.71.3 - - [17/May/2015:08:05:57 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)" +217.168.17.5 - - [17/May/2015:08:05:02 +0000] "GET /downloads/product_2 HTTP/1.1" 404 337 "-" "Debian APT-HTTP/1.3 (0.8.10.3)" +217.168.17.5 - - [17/May/2015:08:05:42 +0000] "GET /downloads/product_1 HTTP/1.1" 404 332 "-" "Debian APT-HTTP/1.3 (0.8.10.3)" +80.91.33.133 - - [17/May/2015:08:05:01 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)" +93.180.71.3 - - [17/May/2015:08:05:27 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)" +217.168.17.5 - - [17/May/2015:08:05:12 +0000] "GET /downloads/product_2 HTTP/1.1" 200 3316 "-" "Some special characters °!"§$%&/()=?`'^²³{[]}\+*~#'-_.:,;µ|<>äöüß" +188.138.60.101 - - [17/May/2015:08:05:49 +0000] "GET /downloads/product_2 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.9.7.9)" +80.91.33.133 - - [17/May/2015:08:05:14 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.16)" +46.4.66.76 - - [17/May/2015:08:05:45 +0000] "GET /downloads/product_1 HTTP/1.1" 404 318 "-" "Debian APT-HTTP/1.3 (1.0.1ubuntu2)" +93.180.71.3 - - [17/May/2015:08:05:26 +0000] "GET /downloads/product_1 HTTP/1.1" 404 324 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)" +91.234.194.89 - - [17/May/2015:08:05:22 +0000] "GET /downloads/product_2 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.9.7.9)" +80.91.33.133 - - [17/May/2015:08:05:07 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)" +37.26.93.214 - - [17/May/2015:08:05:38 +0000] "GET /downloads/product_2 HTTP/1.1" 404 319 "-" "Go 1.1 package http" +188.138.60.101 - - [17/May/2015:08:05:25 +0000] "GET /downloads/product_2 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.9.7.9)" +93.180.71.3 - - [17/May/2015:08:05:11 +0000] "GET /downloads/product_1 HTTP/1.1" 404 340 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)" diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts index 5a8b9bfc114ee..0833f84960ea6 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts @@ -6,7 +6,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { ML_JOB_FIELD_TYPES } from '../../../../../plugins/ml/common/constants/field_types'; -import { FieldVisConfig } from '../../../../../plugins/ml/public/application/datavisualizer/index_based/common'; +import { FieldVisConfig } from '../../../../../plugins/ml/public/application/datavisualizer/stats_table/types'; interface MetricFieldVisConfig extends FieldVisConfig { statsMaxDecimalPlaces: number; diff --git a/x-pack/test/functional/services/ml/data_visualizer_table.ts b/x-pack/test/functional/services/ml/data_visualizer_table.ts index f8623842a596d..ad4625ed4dcb4 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_table.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_table.ts @@ -246,7 +246,8 @@ export function MachineLearningDataVisualizerTableProvider( public async assertNumberFieldContents( fieldName: string, docCountFormatted: string, - topValuesCount: number + topValuesCount: number, + checkDistributionPreviewExist = true ) { await this.assertRowExists(fieldName); await this.assertFieldDocCount(fieldName, docCountFormatted); @@ -257,7 +258,9 @@ export function MachineLearningDataVisualizerTableProvider( await testSubjects.existOrFail(this.detailsSelector(fieldName, 'mlTopValues')); await this.assertTopValuesContents(fieldName, topValuesCount); - await this.assertDistributionPreviewExist(fieldName); + if (checkDistributionPreviewExist) { + await this.assertDistributionPreviewExist(fieldName); + } await this.ensureDetailsClosed(fieldName); } @@ -320,5 +323,19 @@ export function MachineLearningDataVisualizerTableProvider( await this.assertTextFieldContents(fieldName, docCountFormatted, exampleCount); } } + + public async ensureNumRowsPerPage(n: 10 | 25 | 100) { + const paginationButton = 'mlDataVisualizerTable > tablePaginationPopoverButton'; + await retry.tryForTime(10000, async () => { + await testSubjects.existOrFail(paginationButton); + await testSubjects.click(paginationButton); + await testSubjects.click(`tablePagination-${n}-rows`); + + const visibleTexts = await testSubjects.getVisibleText(paginationButton); + + const [, pagination] = visibleTexts.split(': '); + expect(pagination).to.eql(n.toString()); + }); + } })(); } From fd9697c81321e78445ab9376e8eaf108701c994d Mon Sep 17 00:00:00 2001 From: Dan Panzarella Date: Wed, 20 Jan 2021 12:20:24 -0500 Subject: [PATCH 15/83] Rename test spec file (#88842) --- .../timelines/{local_storage.sepc.ts => local_storage.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename x-pack/plugins/security_solution/cypress/integration/timelines/{local_storage.sepc.ts => local_storage.spec.ts} (100%) diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/local_storage.sepc.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/local_storage.spec.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/integration/timelines/local_storage.sepc.ts rename to x-pack/plugins/security_solution/cypress/integration/timelines/local_storage.spec.ts From a0af6bdea629fb77ca65605544a6473c1da3a079 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Wed, 20 Jan 2021 12:21:11 -0500 Subject: [PATCH 16/83] [CI] [TeamCity] Add more default ci groups and build usage_collection plugin (#88864) --- .ci/teamcity/default/build_plugins.sh | 1 + .teamcity/src/builds/default/DefaultCiGroups.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.ci/teamcity/default/build_plugins.sh b/.ci/teamcity/default/build_plugins.sh index 76c553b4f8fa2..4b87596392239 100755 --- a/.ci/teamcity/default/build_plugins.sh +++ b/.ci/teamcity/default/build_plugins.sh @@ -14,6 +14,7 @@ node scripts/build_kibana_platform_plugins \ --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ --scan-dir "$XPACK_DIR/test/licensing_plugin/plugins" \ + --scan-dir "$XPACK_DIR/test/usage_collection/plugins" \ --verbose tc_end_block "Build Platform Plugins" diff --git a/.teamcity/src/builds/default/DefaultCiGroups.kt b/.teamcity/src/builds/default/DefaultCiGroups.kt index 4f39283149e73..948e2ab5782f9 100644 --- a/.teamcity/src/builds/default/DefaultCiGroups.kt +++ b/.teamcity/src/builds/default/DefaultCiGroups.kt @@ -3,7 +3,7 @@ package builds.default import dependsOn import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -const val DEFAULT_CI_GROUP_COUNT = 11 +const val DEFAULT_CI_GROUP_COUNT = 13 val defaultCiGroups = (1..DEFAULT_CI_GROUP_COUNT).map { DefaultCiGroup(it) } object DefaultCiGroups : BuildType({ From 4878554cc96be9073cd47283ccd757dec333d8c3 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Wed, 20 Jan 2021 17:22:16 +0000 Subject: [PATCH 17/83] [Task Manager] cancel expired tasks as part of the available workers check (#88483) When a task expires it continues to reside in the queue until `TaskPool.cancelExpiredTasks()` is called. We call this in `TaskPool.run()`, but `run` won't get called if there is no capacity, as we gate the poller on `TaskPool.availableWorkers()` and that means that if you have as many expired tasks as you have workers - your poller will continually restart but the queue will remain full and that Task Manager is then in capable of taking on any more work. This is what caused `[Task Poller Monitor]: Observable Monitor: Hung Observable...` --- .../task_manager/server/task_pool.test.ts | 57 ++++++++++++++++++- .../plugins/task_manager/server/task_pool.ts | 27 +++++---- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/task_manager/server/task_pool.test.ts b/x-pack/plugins/task_manager/server/task_pool.test.ts index 95768bb2f1afa..9161bbf3c28a5 100644 --- a/x-pack/plugins/task_manager/server/task_pool.test.ts +++ b/x-pack/plugins/task_manager/server/task_pool.test.ts @@ -210,7 +210,8 @@ describe('TaskPool', () => { logger, }); - const expired = resolvable(); + const readyToExpire = resolvable(); + const taskHasExpired = resolvable(); const shouldRun = sinon.spy(() => Promise.resolve()); const shouldNotRun = sinon.spy(() => Promise.resolve()); const now = new Date(); @@ -218,8 +219,9 @@ describe('TaskPool', () => { { ...mockTask(), async run() { + await readyToExpire; this.isExpired = true; - expired.resolve(); + taskHasExpired.resolve(); await sleep(10); return asOk({ state: {} }); }, @@ -246,9 +248,11 @@ describe('TaskPool', () => { expect(pool.occupiedWorkers).toEqual(2); expect(pool.availableWorkers).toEqual(0); - await expired; + readyToExpire.resolve(); + await taskHasExpired; expect(await pool.run([{ ...mockTask() }])).toBeTruthy(); + sinon.assert.calledOnce(shouldRun); sinon.assert.notCalled(shouldNotRun); @@ -260,6 +264,53 @@ describe('TaskPool', () => { ); }); + test('calls to availableWorkers ensures we cancel expired tasks', async () => { + const pool = new TaskPool({ + maxWorkers$: of(1), + logger: loggingSystemMock.create().get(), + }); + + const taskIsRunning = resolvable(); + const taskHasExpired = resolvable(); + const cancel = sinon.spy(() => Promise.resolve()); + const now = new Date(); + expect( + await pool.run([ + { + ...mockTask(), + async run() { + await sleep(10); + this.isExpired = true; + taskIsRunning.resolve(); + await taskHasExpired; + return asOk({ state: {} }); + }, + get expiration() { + return new Date(now.getTime() + 10); + }, + get startedAt() { + return now; + }, + cancel, + }, + ]) + ).toEqual(TaskPoolRunResult.RunningAtCapacity); + + await taskIsRunning; + + sinon.assert.notCalled(cancel); + expect(pool.occupiedWorkers).toEqual(1); + // The call to `availableWorkers` will clear the expired task so it's 1 instead of 0 + expect(pool.availableWorkers).toEqual(1); + sinon.assert.calledOnce(cancel); + + expect(pool.occupiedWorkers).toEqual(0); + expect(pool.availableWorkers).toEqual(1); + // ensure cancel isn't called twice + sinon.assert.calledOnce(cancel); + taskHasExpired.resolve(); + }); + test('logs if cancellation errors', async () => { const logger = loggingSystemMock.create().get(); const pool = new TaskPool({ diff --git a/x-pack/plugins/task_manager/server/task_pool.ts b/x-pack/plugins/task_manager/server/task_pool.ts index 561a222310f3e..db17e75639ed9 100644 --- a/x-pack/plugins/task_manager/server/task_pool.ts +++ b/x-pack/plugins/task_manager/server/task_pool.ts @@ -85,6 +85,10 @@ export class TaskPool { // this should happen less often than the actual changes to the worker queue // so is lighter than emitting the load every time we add/remove a task from the queue this.load$.next(asTaskManagerStatEvent('load', asOk(this.workerLoad))); + // cancel expired task whenever a call is made to check for capacity + // this ensures that we don't end up with a queue of hung tasks causing both + // the poller and the pool from hanging due to lack of capacity + this.cancelExpiredTasks(); return this.maxWorkers - this.occupiedWorkers; } @@ -96,19 +100,7 @@ export class TaskPool { * @param {TaskRunner[]} tasks * @returns {Promise} */ - public run = (tasks: TaskRunner[]) => { - this.cancelExpiredTasks(); - return this.attemptToRun(tasks); - }; - - public cancelRunningTasks() { - this.logger.debug('Cancelling running tasks.'); - for (const task of this.running) { - this.cancelTask(task); - } - } - - private async attemptToRun(tasks: TaskRunner[]): Promise { + public run = async (tasks: TaskRunner[]): Promise => { const [tasksToRun, leftOverTasks] = partitionListByCount(tasks, this.availableWorkers); if (tasksToRun.length) { performance.mark('attemptToRun_start'); @@ -135,13 +127,20 @@ export class TaskPool { if (leftOverTasks.length) { if (this.availableWorkers) { - return this.attemptToRun(leftOverTasks); + return this.run(leftOverTasks); } return TaskPoolRunResult.RanOutOfCapacity; } else if (!this.availableWorkers) { return TaskPoolRunResult.RunningAtCapacity; } return TaskPoolRunResult.RunningAllClaimedTasks; + }; + + public cancelRunningTasks() { + this.logger.debug('Cancelling running tasks.'); + for (const task of this.running) { + this.cancelTask(task); + } } private handleMarkAsRunning(taskRunner: TaskRunner) { From e21defa448d9bdf3de46201ab84cc88ef1c4e90e Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Wed, 20 Jan 2021 17:23:02 +0000 Subject: [PATCH 18/83] [Task Manager] Reject invalid Timeout values in Task Type Definitions (#88602) This PR adds the following: 1. We now validate the interval passed to `timeout` when a task type definition is registered. 2. replaces usage of `Joi` with `schema-type` --- .../task_manager/server/lib/intervals.ts | 35 ++++-- .../task_manager/server/lib/result_type.ts | 8 ++ x-pack/plugins/task_manager/server/task.ts | 101 +++++++++--------- .../server/task_running/task_runner.test.ts | 54 +--------- .../server/task_running/task_runner.ts | 19 +--- .../server/task_type_dictionary.test.ts | 63 ++++++++++- .../server/task_type_dictionary.ts | 32 ++---- 7 files changed, 157 insertions(+), 155 deletions(-) diff --git a/x-pack/plugins/task_manager/server/lib/intervals.ts b/x-pack/plugins/task_manager/server/lib/intervals.ts index da04dffa4b5d1..b7945ff25d089 100644 --- a/x-pack/plugins/task_manager/server/lib/intervals.ts +++ b/x-pack/plugins/task_manager/server/lib/intervals.ts @@ -12,6 +12,19 @@ export enum IntervalCadence { Hour = 'h', Day = 'd', } + +// Once Babel is updated ot support Typescript 4.x templated types, we can use +// this more accurate and safer compile-time valdiation +// export type Interval = `${number}${IntervalCadence}`; +export type Interval = string; + +export function isInterval(interval: Interval | string): interval is Interval { + const numericAsStr: string = interval.slice(0, -1); + const numeric: number = parseInt(numericAsStr, 10); + const cadence: IntervalCadence | string = interval.slice(-1); + return !(!isCadence(cadence) || isNaN(numeric) || numeric <= 0 || !isNumeric(numericAsStr)); +} + const VALID_CADENCE = new Set(Object.values(IntervalCadence)); const CADENCE_IN_MS: Record = { [IntervalCadence.Second]: 1000, @@ -24,7 +37,7 @@ function isCadence(cadence: IntervalCadence | string): cadence is IntervalCadenc return VALID_CADENCE.has(cadence as IntervalCadence); } -export function asInterval(ms: number): string { +export function asInterval(ms: number): Interval { const secondsRemainder = ms % 1000; const minutesRemainder = ms % 60000; return secondsRemainder ? `${ms}ms` : minutesRemainder ? `${ms / 1000}s` : `${ms / 60000}m`; @@ -34,9 +47,9 @@ export function asInterval(ms: number): string { * Returns a date that is the specified interval from now. Currently, * only minute-intervals and second-intervals are supported. * - * @param {string} interval - An interval of the form `Nm` such as `5m` + * @param {Interval} interval - An interval of the form `Nm` such as `5m` */ -export function intervalFromNow(interval?: string): Date | undefined { +export function intervalFromNow(interval?: Interval): Date | undefined { if (interval === undefined) { return; } @@ -48,9 +61,9 @@ export function intervalFromNow(interval?: string): Date | undefined { * only minute-intervals and second-intervals are supported. * * @param {Date} date - The date to add interval to - * @param {string} interval - An interval of the form `Nm` such as `5m` + * @param {Interval} interval - An interval of the form `Nm` such as `5m` */ -export function intervalFromDate(date: Date, interval?: string): Date | undefined { +export function intervalFromDate(date: Date, interval?: Interval): Date | undefined { if (interval === undefined) { return; } @@ -59,9 +72,11 @@ export function intervalFromDate(date: Date, interval?: string): Date | undefine export function maxIntervalFromDate( date: Date, - ...intervals: Array + ...intervals: Array ): Date | undefined { - const maxSeconds = Math.max(...intervals.filter(isString).map(parseIntervalAsSecond)); + const maxSeconds = Math.max( + ...intervals.filter(isString).map((interval) => parseIntervalAsSecond(interval as Interval)) + ); if (!isNaN(maxSeconds)) { return secondsFromDate(date, maxSeconds); } @@ -91,14 +106,14 @@ export function secondsFromDate(date: Date, secs: number): Date { /** * Verifies that the specified interval matches our expected format. * - * @param {string} interval - An interval such as `5m` or `10s` + * @param {Interval} interval - An interval such as `5m` or `10s` * @returns {number} The interval as seconds */ -export const parseIntervalAsSecond = memoize((interval: string): number => { +export const parseIntervalAsSecond = memoize((interval: Interval): number => { return Math.round(parseIntervalAsMillisecond(interval) / 1000); }); -export const parseIntervalAsMillisecond = memoize((interval: string): number => { +export const parseIntervalAsMillisecond = memoize((interval: Interval): number => { const numericAsStr: string = interval.slice(0, -1); const numeric: number = parseInt(numericAsStr, 10); const cadence: IntervalCadence | string = interval.slice(-1); diff --git a/x-pack/plugins/task_manager/server/lib/result_type.ts b/x-pack/plugins/task_manager/server/lib/result_type.ts index d21c17d3bb5b3..cd1d417c79490 100644 --- a/x-pack/plugins/task_manager/server/lib/result_type.ts +++ b/x-pack/plugins/task_manager/server/lib/result_type.ts @@ -39,6 +39,14 @@ export function isErr(result: Result): result is Err { return !isOk(result); } +export function tryAsResult(fn: () => T): Result { + try { + return asOk(fn()); + } catch (e) { + return asErr(e); + } +} + export async function promiseResult(future: Promise): Promise> { try { return asOk(await future); diff --git a/x-pack/plugins/task_manager/server/task.ts b/x-pack/plugins/task_manager/server/task.ts index e832a95ac3caa..9e2a2a2074a84 100644 --- a/x-pack/plugins/task_manager/server/task.ts +++ b/x-pack/plugins/task_manager/server/task.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import Joi from 'joi'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { Interval, isInterval, parseIntervalAsMillisecond } from './lib/intervals'; +import { isErr, tryAsResult } from './lib/result_type'; /* * Type definitions and validations for tasks. @@ -83,17 +85,8 @@ export interface FailedTaskResult { status: TaskStatus.Failed; } -export const validateRunResult = Joi.object({ - runAt: Joi.date().optional(), - schedule: Joi.object().optional(), - error: Joi.object().optional(), - state: Joi.object().optional(), -}).optional(); - export type RunFunction = () => Promise; - export type CancelFunction = () => Promise; - export interface CancellableTask { run: RunFunction; cancel?: CancelFunction; @@ -101,40 +94,53 @@ export interface CancellableTask { export type TaskRunCreatorFunction = (context: RunContext) => CancellableTask; +export const taskDefinitionSchema = schema.object( + { + /** + * A unique identifier for the type of task being defined. + */ + type: schema.string(), + /** + * A brief, human-friendly title for this task. + */ + title: schema.maybe(schema.string()), + /** + * An optional more detailed description of what this task does. + */ + description: schema.maybe(schema.string()), + /** + * How long, in minutes or seconds, the system should wait for the task to complete + * before it is considered to be timed out. (e.g. '5m', the default). If + * the task takes longer than this, Kibana will send it a kill command and + * the task will be re-attempted. + */ + timeout: schema.string({ + defaultValue: '5m', + }), + /** + * Up to how many times the task should retry when it fails to run. This will + * default to the global variable. + */ + maxAttempts: schema.maybe( + schema.number({ + min: 1, + }) + ), + }, + { + validate({ timeout }) { + if (!isInterval(timeout) || isErr(tryAsResult(() => parseIntervalAsMillisecond(timeout)))) { + return `Invalid timeout "${timeout}". Timeout must be of the form "{number}{cadance}" where number is an integer. Example: 5m.`; + } + }, + } +); + /** * Defines a task which can be scheduled and run by the Kibana * task manager. */ -export interface TaskDefinition { - /** - * A unique identifier for the type of task being defined. - */ - type: string; - - /** - * A brief, human-friendly title for this task. - */ - title: string; - - /** - * An optional more detailed description of what this task does. - */ - description?: string; - - /** - * How long, in minutes or seconds, the system should wait for the task to complete - * before it is considered to be timed out. (e.g. '5m', the default). If - * the task takes longer than this, Kibana will send it a kill command and - * the task will be re-attempted. - */ - timeout?: string; - - /** - * Up to how many times the task should retry when it fails to run. This will - * default to the global variable. - */ - maxAttempts?: number; - +export type TaskDefinition = TypeOf & { /** * Function that customizes how the task should behave when the task fails. This * function can return `true`, `false` or a Date. True will tell task manager @@ -149,17 +155,7 @@ export interface TaskDefinition { * and an optional cancel function which cancels the task. */ createTaskRunner: TaskRunCreatorFunction; -} - -export const validateTaskDefinition = Joi.object({ - type: Joi.string().required(), - title: Joi.string().optional(), - description: Joi.string().optional(), - timeout: Joi.string().default('5m'), - maxAttempts: Joi.number().min(1).optional(), - createTaskRunner: Joi.func().required(), - getRetry: Joi.func().optional(), -}).default(); +}; export enum TaskStatus { Idle = 'idle', @@ -174,12 +170,11 @@ export enum TaskLifecycleResult { } export type TaskLifecycle = TaskStatus | TaskLifecycleResult; - export interface IntervalSchedule { /** * An interval in minutes (e.g. '5m'). If specified, this is a recurring task. * */ - interval: string; + interval: Interval; } /* diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts index 3777d89ce63dd..77434d2b6559c 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts @@ -10,10 +10,10 @@ import { secondsFromNow } from '../lib/intervals'; import { asOk, asErr } from '../lib/result_type'; import { TaskManagerRunner, TaskRunResult } from '../task_running'; import { TaskEvent, asTaskRunEvent, asTaskMarkRunningEvent, TaskRun } from '../task_events'; -import { ConcreteTaskInstance, TaskStatus, TaskDefinition, SuccessfulRunResult } from '../task'; +import { ConcreteTaskInstance, TaskStatus } from '../task'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import moment from 'moment'; -import { TaskTypeDictionary } from '../task_type_dictionary'; +import { TaskDefinitionRegistry, TaskTypeDictionary } from '../task_type_dictionary'; import { mockLogger } from '../test_utils'; import { throwUnrecoverableError } from './errors'; @@ -41,24 +41,6 @@ describe('TaskManagerRunner', () => { expect(runner.toString()).toEqual('bar "foo"'); }); - test('warns if the task returns an unexpected result', async () => { - await allowsReturnType(undefined); - await allowsReturnType({}); - await allowsReturnType({ - runAt: new Date(), - }); - await allowsReturnType({ - error: new Error('Dang it!'), - }); - await allowsReturnType({ - state: { shazm: true }, - }); - await disallowsReturnType('hm....'); - await disallowsReturnType({ - whatIsThis: '?!!?', - }); - }); - test('queues a reattempt if the task fails', async () => { const initialAttempts = _.random(0, 2); const id = Date.now().toString(); @@ -1121,7 +1103,7 @@ describe('TaskManagerRunner', () => { interface TestOpts { instance?: Partial; - definitions?: Record>; + definitions?: TaskDefinitionRegistry; onTaskEvent?: (event: TaskEvent) => void; } @@ -1196,34 +1178,4 @@ describe('TaskManagerRunner', () => { instance, }; } - - async function testReturn(result: unknown, shouldBeValid: boolean) { - const { runner, logger } = testOpts({ - definitions: { - bar: { - title: 'Bar!', - createTaskRunner: () => ({ - run: async () => result as SuccessfulRunResult, - }), - }, - }, - }); - - await runner.run(); - - if (shouldBeValid) { - expect(logger.warn).not.toHaveBeenCalled(); - } else { - expect(logger.warn).toHaveBeenCalledTimes(1); - expect(logger.warn.mock.calls[0][0]).toMatch(/invalid task result/i); - } - } - - function allowsReturnType(result: unknown) { - return testReturn(result, true); - } - - function disallowsReturnType(result: unknown) { - return testReturn(result, false); - } }); diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index d281a65da332c..704386d88ea3a 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -13,7 +13,6 @@ import { Logger } from 'src/core/server'; import apm from 'elastic-apm-node'; import { performance } from 'perf_hooks'; -import Joi from 'joi'; import { identity, defaults, flow } from 'lodash'; import { Middleware } from '../lib/middleware'; @@ -36,7 +35,6 @@ import { FailedRunResult, FailedTaskResult, TaskDefinition, - validateRunResult, TaskStatus, } from '../task'; import { TaskTypeDictionary } from '../task_type_dictionary'; @@ -311,20 +309,9 @@ export class TaskManagerRunner implements TaskRunner { private validateResult( result?: SuccessfulRunResult | FailedRunResult | void ): Result { - const { error } = Joi.validate(result, validateRunResult); - - if (error) { - this.logger.warn(`Invalid task result for ${this}: ${error.message}`); - return asErr({ - error: new Error(`Invalid task result for ${this}: ${error.message}`), - state: {}, - }); - } - if (!result) { - return asOk(EMPTY_RUN_RESULT); - } - - return isFailedRunResult(result) ? asErr({ ...result, error: result.error }) : asOk(result); + return isFailedRunResult(result) + ? asErr({ ...result, error: result.error }) + : asOk(result || EMPTY_RUN_RESULT); } private shouldTryToScheduleRetry(): boolean { diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts index e1d6ef17f5f9d..bd532c38725dd 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts @@ -6,7 +6,7 @@ import { get } from 'lodash'; import { RunContext, TaskDefinition } from './task'; -import { sanitizeTaskDefinitions } from './task_type_dictionary'; +import { sanitizeTaskDefinitions, TaskDefinitionRegistry } from './task_type_dictionary'; interface Opts { numTasks: number; @@ -73,8 +73,9 @@ describe('taskTypeDictionary', () => { it('throws a validation exception for invalid task definition', () => { const runsanitize = () => { - const taskDefinitions = { + const taskDefinitions: TaskDefinitionRegistry = { some_kind_of_task: { + // @ts-ignore fail: 'extremely', // cause a validation failure type: 'breaky_task', title: 'Test XYZ', @@ -94,6 +95,62 @@ describe('taskTypeDictionary', () => { return sanitizeTaskDefinitions(taskDefinitions); }; - expect(runsanitize).toThrowError(); + expect(runsanitize).toThrowErrorMatchingInlineSnapshot( + `"[fail]: definition for this key is missing"` + ); + }); + + it('throws a validation exception for invalid timeout on task definition', () => { + const runsanitize = () => { + const taskDefinitions: TaskDefinitionRegistry = { + some_kind_of_task: { + title: 'Test XYZ', + timeout: '15 days', + description: `Actually this won't work`, + createTaskRunner() { + return { + async run() { + return { + state: {}, + }; + }, + }; + }, + }, + }; + + return sanitizeTaskDefinitions(taskDefinitions); + }; + + expect(runsanitize).toThrowErrorMatchingInlineSnapshot( + `"Invalid timeout \\"15 days\\". Timeout must be of the form \\"{number}{cadance}\\" where number is an integer. Example: 5m."` + ); + }); + + it('throws a validation exception for invalid floating point timeout on task definition', () => { + const runsanitize = () => { + const taskDefinitions: TaskDefinitionRegistry = { + some_kind_of_task: { + title: 'Test XYZ', + timeout: '1.5h', + description: `Actually this won't work`, + createTaskRunner() { + return { + async run() { + return { + state: {}, + }; + }, + }; + }, + }, + }; + + return sanitizeTaskDefinitions(taskDefinitions); + }; + + expect(runsanitize).toThrowErrorMatchingInlineSnapshot( + `"Invalid timeout \\"1.5h\\". Timeout must be of the form \\"{number}{cadance}\\" where number is an integer. Example: 5m."` + ); }); }); diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.ts index 451b5dd7cad52..c66b117bde882 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.ts @@ -3,23 +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 Joi from 'joi'; -import { TaskDefinition, validateTaskDefinition } from './task'; +import { TaskDefinition, taskDefinitionSchema } from './task'; import { Logger } from '../../../../src/core/server'; -/* - * The TaskManager is the public interface into the task manager system. This glues together - * all of the disparate modules in one integration point. The task manager operates in two different ways: - * - * - pre-init, it allows middleware registration, but disallows task manipulation - * - post-init, it disallows middleware registration, but allows task manipulation - * - * Due to its complexity, this is mostly tested by integration tests (see readme). - */ - -/** - * The public interface into the task manager system. - */ +export type TaskDefinitionRegistry = Record< + string, + Omit & Pick, 'timeout'> +>; export class TaskTypeDictionary { private definitions = new Map(); private logger: Logger; @@ -57,7 +47,7 @@ export class TaskTypeDictionary { * Method for allowing consumers to register task definitions into the system. * @param taskDefinitions - The Kibana task definitions dictionary */ - public registerTaskDefinitions(taskDefinitions: Record>) { + public registerTaskDefinitions(taskDefinitions: TaskDefinitionRegistry) { const duplicate = Object.keys(taskDefinitions).find((type) => this.definitions.has(type)); if (duplicate) { throw new Error(`Task ${duplicate} is already defined!`); @@ -79,10 +69,8 @@ export class TaskTypeDictionary { * * @param taskDefinitions - The Kibana task definitions dictionary */ -export function sanitizeTaskDefinitions( - taskDefinitions: Record> -): TaskDefinition[] { - return Object.entries(taskDefinitions).map(([type, rawDefinition]) => - Joi.attempt({ type, ...rawDefinition }, validateTaskDefinition) - ); +export function sanitizeTaskDefinitions(taskDefinitions: TaskDefinitionRegistry): TaskDefinition[] { + return Object.entries(taskDefinitions).map(([type, rawDefinition]) => { + return taskDefinitionSchema.validate({ type, ...rawDefinition }) as TaskDefinition; + }); } From 2e878f59f7dae4422dbf8db0a43a2bf3160be0c3 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Wed, 20 Jan 2021 11:29:07 -0600 Subject: [PATCH 19/83] Sync search query with url in advanced settings (#81829) --- src/plugins/advanced_settings/kibana.json | 2 +- .../management_app/advanced_settings.test.tsx | 62 ++++++++-- .../management_app/advanced_settings.tsx | 111 +++++++++++------- .../components/search/search.tsx | 13 +- .../management_app/lib/get_aria_name.test.ts | 9 ++ .../management_app/lib/get_aria_name.ts | 39 +++++- .../mount_management_section.tsx | 28 ++++- .../common/url/encode_uri_query.test.ts | 77 +++++++++++- .../common/url/encode_uri_query.ts | 33 +++++- src/plugins/kibana_utils/common/url/index.ts | 3 +- .../telemetry_management_section.tsx | 40 ++++--- 11 files changed, 324 insertions(+), 93 deletions(-) diff --git a/src/plugins/advanced_settings/kibana.json b/src/plugins/advanced_settings/kibana.json index df0d31a904c59..68b133f382c35 100644 --- a/src/plugins/advanced_settings/kibana.json +++ b/src/plugins/advanced_settings/kibana.json @@ -5,5 +5,5 @@ "ui": true, "requiredPlugins": ["management"], "optionalPlugins": ["home", "usageCollection"], - "requiredBundles": ["kibanaReact", "home"] + "requiredBundles": ["kibanaReact", "kibanaUtils", "home"] } diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx index f6490e2560c5f..aaa7b1c10a412 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Observable } from 'rxjs'; import { ReactWrapper } from 'enzyme'; -import { mountWithI18nProvider } from '@kbn/test/jest'; +import { mountWithI18nProvider, shallowWithI18nProvider } from '@kbn/test/jest'; import dedent from 'dedent'; import { PublicUiSettingsParams, @@ -17,9 +17,10 @@ import { UiSettingsType, } from '../../../../core/public'; import { FieldSetting } from './types'; -import { AdvancedSettingsComponent } from './advanced_settings'; +import { AdvancedSettings } from './advanced_settings'; import { notificationServiceMock, docLinksServiceMock } from '../../../../core/public/mocks'; import { ComponentRegistry } from '../component_registry'; +import { Search } from './components/search'; jest.mock('./components/field', () => ({ Field: () => { @@ -222,10 +223,30 @@ function mockConfig() { } describe('AdvancedSettings', () => { + const defaultQuery = 'test:string:setting'; + const mockHistory = { + listen: jest.fn(), + } as any; + const locationSpy = jest.spyOn(window, 'location', 'get'); + + afterAll(() => { + locationSpy.mockRestore(); + }); + + const mockQuery = (query = defaultQuery) => { + locationSpy.mockImplementation( + () => + ({ + search: `?query=${query}`, + } as any) + ); + }; + it('should render specific setting if given setting key', async () => { + mockQuery(); const component = mountWithI18nProvider( - { component .find('Field') .filterWhere( - (n: ReactWrapper) => - (n.prop('setting') as Record).name === 'test:string:setting' + (n: ReactWrapper) => (n.prop('setting') as Record).name === defaultQuery ) ).toHaveLength(1); }); it('should render read-only when saving is disabled', async () => { + mockQuery(); const component = mountWithI18nProvider( - { component .find('Field') .filterWhere( - (n: ReactWrapper) => - (n.prop('setting') as Record).name === 'test:string:setting' + (n: ReactWrapper) => (n.prop('setting') as Record).name === defaultQuery ) .prop('enableSaving') ).toBe(false); }); + + it('should render unfiltered with query parsing error', async () => { + const badQuery = 'category:(accessibility))'; + mockQuery(badQuery); + const { toasts } = notificationServiceMock.createStartContract(); + const getComponent = () => + shallowWithI18nProvider( + + ); + + expect(getComponent).not.toThrow(); + expect(toasts.addWarning).toHaveBeenCalledTimes(1); + const component = getComponent(); + expect(component.find(Search).prop('query').text).toEqual(''); + }); }); diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx index 1b38e9356cbb2..b9b447f739fad 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx @@ -8,22 +8,35 @@ import React, { Component } from 'react'; import { Subscription } from 'rxjs'; -import { Comparators, EuiFlexGroup, EuiFlexItem, EuiSpacer, Query } from '@elastic/eui'; +import { UnregisterCallback } from 'history'; +import { parse } from 'query-string'; -import { useParams } from 'react-router-dom'; import { UiCounterMetricType } from '@kbn/analytics'; +import { Comparators, EuiFlexGroup, EuiFlexItem, EuiSpacer, Query } from '@elastic/eui'; + +import { + IUiSettingsClient, + DocLinksStart, + ToastsStart, + ScopedHistory, +} from '../../../../core/public'; +import { url } from '../../../kibana_utils/public'; + import { CallOuts } from './components/call_outs'; import { Search } from './components/search'; import { Form } from './components/form'; import { AdvancedSettingsVoiceAnnouncement } from './components/advanced_settings_voice_announcement'; -import { IUiSettingsClient, DocLinksStart, ToastsStart } from '../../../../core/public/'; import { ComponentRegistry } from '../'; import { getAriaName, toEditableConfig, DEFAULT_CATEGORY } from './lib'; import { FieldSetting, SettingsChanges } from './types'; +import { parseErrorMsg } from './components/search/search'; + +export const QUERY = 'query'; interface AdvancedSettingsProps { + history: ScopedHistory; enableSaving: boolean; uiSettings: IUiSettingsClient; dockLinks: DocLinksStart['links']; @@ -32,10 +45,6 @@ interface AdvancedSettingsProps { trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; } -interface AdvancedSettingsComponentProps extends AdvancedSettingsProps { - queryText: string; -} - interface AdvancedSettingsState { footerQueryMatched: boolean; query: Query; @@ -44,30 +53,25 @@ interface AdvancedSettingsState { type GroupedSettings = Record; -export class AdvancedSettingsComponent extends Component< - AdvancedSettingsComponentProps, - AdvancedSettingsState -> { +export class AdvancedSettings extends Component { private settings: FieldSetting[]; private groupedSettings: GroupedSettings; private categoryCounts: Record; private categories: string[] = []; private uiSettingsSubscription?: Subscription; + private unregister: UnregisterCallback; - constructor(props: AdvancedSettingsComponentProps) { + constructor(props: AdvancedSettingsProps) { super(props); this.settings = this.initSettings(this.props.uiSettings); this.groupedSettings = this.initGroupedSettings(this.settings); this.categories = this.initCategories(this.groupedSettings); this.categoryCounts = this.initCategoryCounts(this.groupedSettings); - - const parsedQuery = Query.parse(this.props.queryText ? getAriaName(this.props.queryText) : ''); - this.state = { - query: parsedQuery, - footerQueryMatched: false, - filteredSettings: this.mapSettings(Query.execute(parsedQuery, this.settings)), - }; + this.state = this.getQueryState(undefined, true); + this.unregister = this.props.history.listen(({ search }) => { + this.setState(this.getQueryState(search)); + }); } init(config: IUiSettingsClient) { @@ -134,11 +138,50 @@ export class AdvancedSettingsComponent extends Component< } componentWillUnmount() { - if (this.uiSettingsSubscription) { - this.uiSettingsSubscription.unsubscribe(); + this.uiSettingsSubscription?.unsubscribe?.(); + this.unregister?.(); + } + + private getQuery(queryString: string, intialQuery = false): Query { + try { + const query = intialQuery ? getAriaName(queryString) : queryString ?? ''; + return Query.parse(query); + } catch ({ message }) { + this.props.toasts.addWarning({ + title: parseErrorMsg, + text: message, + }); + return Query.parse(''); } } + private getQueryText(search?: string): string { + const queryParams = parse(search ?? window.location.search) ?? {}; + return (queryParams[QUERY] as string) ?? ''; + } + + private getQueryState(search?: string, intialQuery = false): AdvancedSettingsState { + const queryString = this.getQueryText(search); + const query = this.getQuery(queryString, intialQuery); + const filteredSettings = this.mapSettings(Query.execute(query, this.settings)); + const footerQueryMatched = Object.keys(filteredSettings).length > 0; + + return { + query, + filteredSettings, + footerQueryMatched, + }; + } + + setUrlQuery(q: string = '') { + const search = url.addQueryParam(window.location.search, QUERY, q); + + this.props.history.push({ + pathname: '', // remove any route query param + search, + }); + } + mapConfig(config: IUiSettingsClient) { const all = config.getAll(); return Object.entries(all) @@ -167,18 +210,11 @@ export class AdvancedSettingsComponent extends Component< } onQueryChange = ({ query }: { query: Query }) => { - this.setState({ - query, - filteredSettings: this.mapSettings(Query.execute(query, this.settings)), - }); + this.setUrlQuery(query.text); }; clearQuery = () => { - this.setState({ - query: Query.parse(''), - footerQueryMatched: false, - filteredSettings: this.groupedSettings, - }); + this.setUrlQuery(''); }; onFooterQueryMatchChange = (matched: boolean) => { @@ -244,18 +280,3 @@ export class AdvancedSettingsComponent extends Component< ); } } - -export const AdvancedSettings = (props: AdvancedSettingsProps) => { - const { query } = useParams<{ query: string }>(); - return ( - - ); -}; diff --git a/src/plugins/advanced_settings/public/management_app/components/search/search.tsx b/src/plugins/advanced_settings/public/management_app/components/search/search.tsx index cd22c3d6db723..bc875dd8de785 100644 --- a/src/plugins/advanced_settings/public/management_app/components/search/search.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/search/search.tsx @@ -12,12 +12,19 @@ import { EuiSearchBar, EuiFormErrorText, Query } from '@elastic/eui'; import { getCategoryName } from '../../lib'; +export const CATEGORY_FIELD = 'category'; + interface SearchProps { categories: string[]; query: Query; onQueryChange: ({ query }: { query: Query }) => void; } +export const parseErrorMsg = i18n.translate( + 'advancedSettings.searchBar.unableToParseQueryErrorMessage', + { defaultMessage: 'Unable to parse query' } +); + export class Search extends PureComponent { private categories: Array<{ value: string; name: string }> = []; @@ -67,7 +74,7 @@ export class Search extends PureComponent { const filters = [ { type: 'field_value_selection' as const, - field: 'category', + field: CATEGORY_FIELD, name: i18n.translate('advancedSettings.categorySearchLabel', { defaultMessage: 'Category', }), @@ -78,10 +85,6 @@ export class Search extends PureComponent { let queryParseError; if (!this.state.isSearchTextValid) { - const parseErrorMsg = i18n.translate( - 'advancedSettings.searchBar.unableToParseQueryErrorMessage', - { defaultMessage: 'Unable to parse query' } - ); queryParseError = ( {`${parseErrorMsg}. ${this.state.parseErrorMessage}`} ); diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts b/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts index abe0e37d606a4..c00ae028dfc4d 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.test.ts @@ -22,6 +22,15 @@ describe('Settings', function () { expect(getAriaName()).to.be(''); expect(getAriaName(undefined)).to.be(''); }); + + it('should preserve category string', function () { + expect(getAriaName('xPack:fooBar:foo_bar_baz category:(general)')).to.be( + 'x pack foo bar foo bar baz category:(general)' + ); + expect(getAriaName('xPack:fooBar:foo_bar_baz category:(general or discover)')).to.be( + 'x pack foo bar foo bar baz category:(general or discover)' + ); + }); }); }); }); diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts b/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts index 1fe43fc6ba868..6d4817934083d 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/get_aria_name.ts @@ -8,15 +8,46 @@ import { words } from 'lodash'; +import { Query } from '@elastic/eui'; + +import { CATEGORY_FIELD } from '../components/search/search'; + +const mapWords = (name?: string): string => + words(name ?? '') + .map((word) => word.toLowerCase()) + .join(' '); + /** * @name {string} the name of the configuration object * @returns {string} a space delimited, lowercase string with * special characters removed. * - * Example: 'xPack:fooBar:foo_bar_baz' -> 'x pack foo bar foo bar baz' + * Examples: + * - `xPack:fooBar:foo_bar_baz` -> `x pack foo bar foo bar baz` + * - `xPack:fooBar:foo_bar_baz category:(general)` -> `x pack foo bar foo bar baz category:(general)` */ export function getAriaName(name?: string) { - return words(name || '') - .map((word) => word.toLowerCase()) - .join(' '); + if (!name) { + return ''; + } + + const query = Query.parse(name); + + if (query.hasOrFieldClause(CATEGORY_FIELD)) { + const categories = query.getOrFieldClause(CATEGORY_FIELD); + const termValue = mapWords(query.removeOrFieldClauses(CATEGORY_FIELD).text); + + if (!categories || !Array.isArray(categories.value)) { + return termValue; + } + + let categoriesQuery = Query.parse(''); + categories.value.forEach((v) => { + categoriesQuery = categoriesQuery.addOrFieldValue(CATEGORY_FIELD, v); + }); + + return `${termValue} ${categoriesQuery.text}`; + } + + return mapWords(name); } diff --git a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx index b48f3eff7453a..21a8a8cbd05ab 100644 --- a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx +++ b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx @@ -8,18 +8,21 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Router, Switch, Route } from 'react-router-dom'; +import { Router, Switch, Route, Redirect, RouteChildrenProps } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; -import { StartServicesAccessor } from 'src/core/public'; -import { AdvancedSettings } from './advanced_settings'; +import { LocationDescriptor } from 'history'; +import { url } from '../../../kibana_utils/public'; import { ManagementAppMountParams } from '../../../management/public'; +import { UsageCollectionSetup } from '../../../usage_collection/public'; +import { StartServicesAccessor } from '../../../../core/public'; + +import { AdvancedSettings, QUERY } from './advanced_settings'; import { ComponentRegistry } from '../types'; import './index.scss'; -import { UsageCollectionSetup } from '../../../usage_collection/public'; const title = i18n.translate('advancedSettings.advancedSettingsLabel', { defaultMessage: 'Advanced Settings', @@ -36,6 +39,18 @@ const readOnlyBadge = { iconType: 'glasses', }; +const redirectUrl = ({ + match, + location, +}: RouteChildrenProps<{ [QUERY]: string }>): LocationDescriptor => { + const search = url.addQueryParam(location.search, QUERY, match?.params[QUERY]); + + return { + pathname: '/', + search, + }; +}; + export async function mountManagementSection( getStartServices: StartServicesAccessor, params: ManagementAppMountParams, @@ -56,8 +71,11 @@ export async function mountManagementSection( - + {/* TODO: remove route param (`query`) in 7.13 */} + {(props) => } + { test('should correctly encode uri query and not encode chars defined as pchar set in rfc3986', () => { @@ -57,3 +57,78 @@ describe('encodeQuery', () => { }); }); }); + +describe('addQueryParam', () => { + const sampleParams = '?myNumber=23&myString=test&myValue=&myBoolean=false'; + + describe('setting values', () => { + it('should perserve other values', () => { + expect(addQueryParam(sampleParams, 'myNewValue', 'test')).toEqual( + 'myBoolean=false&myNumber=23&myString=test&myValue=&myNewValue=test' + ); + }); + + it('should set boolean values', () => { + expect(addQueryParam('', 'myBoolean', 'false')).toEqual('myBoolean=false'); + expect(addQueryParam('', 'myBoolean', 'true')).toEqual('myBoolean=true'); + }); + + it('should set string values', () => { + expect(addQueryParam('', 'myString', 'test')).toEqual('myString=test'); + expect(addQueryParam('', 'myString', '')).toEqual('myString='); + }); + + it('should set number values', () => { + expect(addQueryParam('', 'myNumber', '23')).toEqual('myNumber=23'); + expect(addQueryParam('', 'myNumber', '0')).toEqual('myNumber=0'); + }); + }); + + describe('changing values', () => { + it('should perserve other values', () => { + expect(addQueryParam(sampleParams, 'myBoolean', 'true')).toEqual( + 'myBoolean=true&myNumber=23&myString=test&myValue=' + ); + }); + + it('should change boolean value', () => { + expect(addQueryParam('?myBoolean=true', 'myBoolean', 'false')).toEqual('myBoolean=false'); + expect(addQueryParam('?myBoolean=false', 'myBoolean', 'true')).toEqual('myBoolean=true'); + }); + + it('should change string values', () => { + expect(addQueryParam('?myString=initial', 'myString', 'test')).toEqual('myString=test'); + expect(addQueryParam('?myString=initial', 'myString', '')).toEqual('myString='); + }); + + it('should change number values', () => { + expect(addQueryParam('?myNumber=1', 'myNumber', '23')).toEqual('myNumber=23'); + expect(addQueryParam('?myNumber=1', 'myNumber', '0')).toEqual('myNumber=0'); + }); + }); + + describe('deleting values', () => { + it('should perserve other values', () => { + expect(addQueryParam(sampleParams, 'myNumber')).toEqual( + 'myBoolean=false&myString=test&myValue=' + ); + }); + + it('should delete empty values', () => { + expect(addQueryParam('?myValue=', 'myValue')).toEqual(''); + }); + + it('should delete boolean values', () => { + expect(addQueryParam('?myBoolean=false', 'myBoolean')).toEqual(''); + expect(addQueryParam('?myBoolean=true', 'myBoolean')).toEqual(''); + }); + + it('should delete string values', () => { + expect(addQueryParam('?myString=test', 'myString')).toEqual(''); + }); + + it('should delete number values', () => { + expect(addQueryParam('?myNumber=23', 'myNumber')).toEqual(''); + }); + }); +}); diff --git a/src/plugins/kibana_utils/common/url/encode_uri_query.ts b/src/plugins/kibana_utils/common/url/encode_uri_query.ts index ebc267c352227..c5fae36b13459 100644 --- a/src/plugins/kibana_utils/common/url/encode_uri_query.ts +++ b/src/plugins/kibana_utils/common/url/encode_uri_query.ts @@ -6,7 +6,7 @@ * Public License, v 1. */ -import { ParsedQuery } from 'query-string'; +import { ParsedQuery, parse, stringify } from 'query-string'; import { transform } from 'lodash'; /** @@ -32,15 +32,38 @@ export function encodeUriQuery(val: string, pctEncodeSpaces = false) { export const encodeQuery = ( query: ParsedQuery, - encodeFunction: (val: string, pctEncodeSpaces?: boolean) => string = encodeUriQuery -) => - transform(query, (result: any, value, key) => { + encodeFunction: (val: string, pctEncodeSpaces?: boolean) => string = encodeUriQuery, + pctEncodeSpaces = true +): ParsedQuery => + transform(query, (result, value, key) => { if (key) { const singleValue = Array.isArray(value) ? value.join(',') : value; result[key] = encodeFunction( singleValue === undefined || singleValue === null ? '' : singleValue, - true + pctEncodeSpaces ); } }); + +/** + * Method to help modify url query params. + * + * @param params + * @param key + * @param value + */ +export const addQueryParam = (params: string, key: string, value?: string) => { + const queryParams = parse(params); + + if (value !== undefined) { + queryParams[key] = value; + } else { + delete queryParams[key]; + } + + return stringify(encodeQuery(queryParams, undefined, false), { + sort: false, + encode: false, + }); +}; diff --git a/src/plugins/kibana_utils/common/url/index.ts b/src/plugins/kibana_utils/common/url/index.ts index b2705a45fc4d7..fffef45dbe897 100644 --- a/src/plugins/kibana_utils/common/url/index.ts +++ b/src/plugins/kibana_utils/common/url/index.ts @@ -6,9 +6,10 @@ * Public License, v 1. */ -import { encodeUriQuery, encodeQuery } from './encode_uri_query'; +import { encodeUriQuery, encodeQuery, addQueryParam } from './encode_uri_query'; export const url = { encodeQuery, encodeUriQuery, + addQueryParam, }; diff --git a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx index 3b69544bd63db..ede209b772a4e 100644 --- a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx +++ b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx @@ -38,7 +38,7 @@ interface Props { isSecurityExampleEnabled: () => boolean; showAppliesSettingMessage: boolean; enableSaving: boolean; - query?: any; + query?: { text: string }; toasts: ToastsStart; } @@ -51,34 +51,42 @@ interface State { } export class TelemetryManagementSection extends Component { - state: State = { - processing: false, - showExample: false, - showSecurityExample: false, - queryMatches: null, - enabled: this.props.telemetryService.getIsOptedIn() || false, - }; + constructor(props: Props) { + super(props); + + this.state = { + processing: false, + showExample: false, + showSecurityExample: false, + queryMatches: props.query ? this.checkQueryMatch(props.query) : null, + enabled: this.props.telemetryService.getIsOptedIn() || false, + }; + } UNSAFE_componentWillReceiveProps(nextProps: Props) { const { query } = nextProps; + const queryMatches = this.checkQueryMatch(query); - const searchTerm = (query.text || '').toLowerCase(); - const searchTermMatches = - this.props.telemetryService.getCanChangeOptInStatus() && - SEARCH_TERMS.some((term) => term.indexOf(searchTerm) >= 0); - - if (searchTermMatches !== this.state.queryMatches) { + if (queryMatches !== this.state.queryMatches) { this.setState( { - queryMatches: searchTermMatches, + queryMatches, }, () => { - this.props.onQueryMatchChange(searchTermMatches); + this.props.onQueryMatchChange(queryMatches); } ); } } + checkQueryMatch(query?: { text: string }): boolean { + const searchTerm = (query?.text ?? '').toLowerCase(); + return ( + this.props.telemetryService.getCanChangeOptInStatus() && + SEARCH_TERMS.some((term) => term.indexOf(searchTerm) >= 0) + ); + } + render() { const { telemetryService, isSecurityExampleEnabled } = this.props; const { showExample, showSecurityExample, queryMatches, enabled, processing } = this.state; From 466d83c6d18d679fdd6ac0bf4fb63fe2ee091394 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Wed, 20 Jan 2021 13:15:59 -0500 Subject: [PATCH 20/83] [CI] [TeamCity] Enable job triggers in TeamCity (#88869) --- .teamcity/src/Common.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.teamcity/src/Common.kt b/.teamcity/src/Common.kt index 35bc881b88967..2e5357541bfe7 100644 --- a/.teamcity/src/Common.kt +++ b/.teamcity/src/Common.kt @@ -4,7 +4,7 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext const val ENABLE_REPORTING = false // If set to false, jobs with triggers (scheduled, on commit, etc) will be paused -const val ENABLE_TRIGGERS = false +const val ENABLE_TRIGGERS = true fun getProjectBranch(): String { return DslContext.projectName From edb338a8ad4c3917338eb7b274878146015934bf Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Wed, 20 Jan 2021 19:25:32 +0100 Subject: [PATCH 21/83] Add SO import hook / warnings API (#87996) * initial commit * adapt client-side signatures * more type fixes * adapt api IT asserts * fix some unit tests * fix more test usages * fix integration tests * fix FT test assertions * fix FT test assertions * add FTR API integ test suite * create the plugin_api_integration test suite * adapt and fix flyout tests * update documentation * update generated doc * add unit tests for `executeImportHooks` * wire resolve_import_errors and add unit tests * move hooks registration to SO type API * update generated doc * design integration * update generated doc * Add FTR tests for import warnings * deletes plugins api integ tests * self review * move onImport to management definition * update license header * rename actionUrl to actionPath --- .../core/public/kibana-plugin-core-public.md | 3 + ...simportactionrequiredwarning.actionpath.md | 13 ++ ...importactionrequiredwarning.buttonlabel.md | 13 ++ ...savedobjectsimportactionrequiredwarning.md | 25 ++++ ...ectsimportactionrequiredwarning.message.md | 13 ++ ...objectsimportactionrequiredwarning.type.md | 11 ++ ...-core-public.savedobjectsimportresponse.md | 1 + ...lic.savedobjectsimportresponse.warnings.md | 11 ++ ...-public.savedobjectsimportsimplewarning.md | 21 +++ ...savedobjectsimportsimplewarning.message.md | 13 ++ ...ic.savedobjectsimportsimplewarning.type.md | 11 ++ ...n-core-public.savedobjectsimportwarning.md | 15 ++ .../core/server/kibana-plugin-core-server.md | 5 + ...simportactionrequiredwarning.actionpath.md | 13 ++ ...importactionrequiredwarning.buttonlabel.md | 13 ++ ...savedobjectsimportactionrequiredwarning.md | 25 ++++ ...ectsimportactionrequiredwarning.message.md | 13 ++ ...objectsimportactionrequiredwarning.type.md | 11 ++ ...ugin-core-server.savedobjectsimporthook.md | 17 +++ ...ore-server.savedobjectsimporthookresult.md | 20 +++ ...r.savedobjectsimporthookresult.warnings.md | 13 ++ ...-core-server.savedobjectsimportresponse.md | 1 + ...ver.savedobjectsimportresponse.warnings.md | 11 ++ ...-server.savedobjectsimportsimplewarning.md | 21 +++ ...savedobjectsimportsimplewarning.message.md | 13 ++ ...er.savedobjectsimportsimplewarning.type.md | 11 ++ ...n-core-server.savedobjectsimportwarning.md | 15 ++ ...er.savedobjectstypemanagementdefinition.md | 1 + ...bjectstypemanagementdefinition.onimport.md | 52 +++++++ src/core/public/http/base_path.mock.ts | 28 ++++ src/core/public/http/http_service.mock.ts | 2 + src/core/public/index.ts | 3 + src/core/public/public.api.md | 21 +++ src/core/public/saved_objects/index.ts | 3 + src/core/server/index.ts | 5 + .../import/import_saved_objects.test.ts | 110 +++++++++++--- .../import/import_saved_objects.ts | 15 +- src/core/server/saved_objects/import/index.ts | 5 + .../import/lib/execute_import_hooks.test.ts | 135 ++++++++++++++++++ .../import/lib/execute_import_hooks.ts | 42 ++++++ .../server/saved_objects/import/lib/index.ts | 1 + .../import/resolve_import_errors.test.ts | 134 +++++++++++++---- .../import/resolve_import_errors.ts | 13 ++ .../import/saved_objects_importer.ts | 13 ++ src/core/server/saved_objects/import/types.ts | 70 +++++++++ src/core/server/saved_objects/index.ts | 5 + .../routes/integration_tests/import.test.ts | 10 +- .../resolve_import_errors.test.ts | 8 +- .../saved_objects/saved_objects_service.ts | 1 + src/core/server/saved_objects/types.ts | 46 ++++++ src/core/server/server.api.md | 30 ++++ .../public/lib/import_file.ts | 10 +- .../lib/process_import_response.test.ts | 40 +++++- .../public/lib/process_import_response.ts | 3 + .../__snapshots__/flyout.test.tsx.snap | 12 ++ .../objects_table/components/flyout.test.tsx | 4 +- .../objects_table/components/flyout.tsx | 16 ++- .../components/import_summary.test.tsx | 87 +++++++---- .../components/import_summary.tsx | 132 +++++++++++++---- .../objects_table/saved_objects_table.tsx | 1 + .../apis/saved_objects/import.ts | 5 + .../saved_objects/resolve_import_errors.ts | 9 +- .../management/saved_objects_page.ts | 16 +++ test/plugin_functional/config.ts | 1 + .../plugins/saved_object_hooks/kibana.json | 8 ++ .../plugins/saved_object_hooks/package.json | 14 ++ .../saved_object_hooks/server/index.ts | 11 ++ .../saved_object_hooks/server/plugin.ts | 64 +++++++++ .../plugins/saved_object_hooks/tsconfig.json | 18 +++ .../exports/_import_both_types.ndjson | 2 + .../exports/_import_type_1.ndjson | 1 + .../exports/_import_type_2.ndjson | 1 + .../import_warnings.ts | 74 ++++++++++ .../saved_objects_management/index.ts | 15 ++ .../components/copy_to_space_flyout.test.tsx | 10 ++ .../lib/copy_to_spaces/copy_to_spaces.test.ts | 2 + .../resolve_copy_conflicts.test.ts | 2 + .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 79 files changed, 1538 insertions(+), 129 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.buttonlabel.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.message.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.type.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsimportresponse.warnings.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.message.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.type.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsimportwarning.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.buttonlabel.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.message.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.type.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthook.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthookresult.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthookresult.warnings.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimportresponse.warnings.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.message.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.type.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsimportwarning.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md create mode 100644 src/core/public/http/base_path.mock.ts create mode 100644 src/core/server/saved_objects/import/lib/execute_import_hooks.test.ts create mode 100644 src/core/server/saved_objects/import/lib/execute_import_hooks.ts create mode 100644 test/plugin_functional/plugins/saved_object_hooks/kibana.json create mode 100644 test/plugin_functional/plugins/saved_object_hooks/package.json create mode 100644 test/plugin_functional/plugins/saved_object_hooks/server/index.ts create mode 100644 test/plugin_functional/plugins/saved_object_hooks/server/plugin.ts create mode 100644 test/plugin_functional/plugins/saved_object_hooks/tsconfig.json create mode 100644 test/plugin_functional/test_suites/saved_objects_management/exports/_import_both_types.ndjson create mode 100644 test/plugin_functional/test_suites/saved_objects_management/exports/_import_type_1.ndjson create mode 100644 test/plugin_functional/test_suites/saved_objects_management/exports/_import_type_2.ndjson create mode 100644 test/plugin_functional/test_suites/saved_objects_management/import_warnings.ts create mode 100644 test/plugin_functional/test_suites/saved_objects_management/index.ts diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index bfe787f3793d7..efd499823ffad 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -109,12 +109,14 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsFindOptions](./kibana-plugin-core-public.savedobjectsfindoptions.md) | | | [SavedObjectsFindOptionsReference](./kibana-plugin-core-public.savedobjectsfindoptionsreference.md) | | | [SavedObjectsFindResponsePublic](./kibana-plugin-core-public.savedobjectsfindresponsepublic.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | +| [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md) | A warning meant to notify that a specific user action is required to finalize the import of some type of object. The actionUrl must be a path relative to the basePath, and not include it. | | [SavedObjectsImportAmbiguousConflictError](./kibana-plugin-core-public.savedobjectsimportambiguousconflicterror.md) | Represents a failure to import due to a conflict, which can be resolved in different ways with an overwrite. | | [SavedObjectsImportConflictError](./kibana-plugin-core-public.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | | [SavedObjectsImportFailure](./kibana-plugin-core-public.savedobjectsimportfailure.md) | Represents a failure to import. | | [SavedObjectsImportMissingReferencesError](./kibana-plugin-core-public.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | | [SavedObjectsImportResponse](./kibana-plugin-core-public.savedobjectsimportresponse.md) | The response describing the result of an import. | | [SavedObjectsImportRetry](./kibana-plugin-core-public.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | +| [SavedObjectsImportSimpleWarning](./kibana-plugin-core-public.savedobjectsimportsimplewarning.md) | A simple informative warning that will be displayed to the user. | | [SavedObjectsImportSuccess](./kibana-plugin-core-public.savedobjectsimportsuccess.md) | Represents a successful import. | | [SavedObjectsImportUnknownError](./kibana-plugin-core-public.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | | [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-core-public.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | @@ -163,6 +165,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-core-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-core-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-core-public.savedobjectsclient.md) | +| [SavedObjectsImportWarning](./kibana-plugin-core-public.savedobjectsimportwarning.md) | Composite type of all the possible types of import warnings.See [SavedObjectsImportSimpleWarning](./kibana-plugin-core-public.savedobjectsimportsimplewarning.md) and [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md) for more details. | | [SavedObjectsNamespaceType](./kibana-plugin-core-public.savedobjectsnamespacetype.md) | The namespace type dictates how a saved object can be interacted in relation to namespaces. Each type is mutually exclusive: \* single (default): this type of saved object is namespace-isolated, e.g., it exists in only one namespace. \* multiple: this type of saved object is shareable, e.g., it can exist in one or more namespaces. \* agnostic: this type of saved object is global. | | [StartServicesAccessor](./kibana-plugin-core-public.startservicesaccessor.md) | Allows plugins to get access to APIs available in start inside async handlers, such as [App.mount](./kibana-plugin-core-public.app.mount.md). Promise will not resolve until Core and plugin dependencies have completed start. | | [StringValidation](./kibana-plugin-core-public.stringvalidation.md) | Allows regex objects or a regex string | diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md new file mode 100644 index 0000000000000..120a9d5f3386c --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md) > [actionPath](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md) + +## SavedObjectsImportActionRequiredWarning.actionPath property + +The path (without the basePath) that the user should be redirect to to address this warning. + +Signature: + +```typescript +actionPath: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.buttonlabel.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.buttonlabel.md new file mode 100644 index 0000000000000..ae7daba4860ef --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.buttonlabel.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md) > [buttonLabel](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.buttonlabel.md) + +## SavedObjectsImportActionRequiredWarning.buttonLabel property + +An optional label to use for the link button. If unspecified, a default label will be used. + +Signature: + +```typescript +buttonLabel?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md new file mode 100644 index 0000000000000..26d734c39c918 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md) + +## SavedObjectsImportActionRequiredWarning interface + +A warning meant to notify that a specific user action is required to finalize the import of some type of object. + + The `actionUrl` must be a path relative to the basePath, and not include it. + +Signature: + +```typescript +export interface SavedObjectsImportActionRequiredWarning +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [actionPath](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.actionpath.md) | string | The path (without the basePath) that the user should be redirect to to address this warning. | +| [buttonLabel](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.buttonlabel.md) | string | An optional label to use for the link button. If unspecified, a default label will be used. | +| [message](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.message.md) | string | The translated message to display to the user. | +| [type](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.type.md) | 'action_required' | | + diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.message.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.message.md new file mode 100644 index 0000000000000..c0f322892577e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.message.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md) > [message](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.message.md) + +## SavedObjectsImportActionRequiredWarning.message property + +The translated message to display to the user. + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.type.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.type.md new file mode 100644 index 0000000000000..ee88f6a0d5d85 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md) > [type](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.type.md) + +## SavedObjectsImportActionRequiredWarning.type property + +Signature: + +```typescript +type: 'action_required'; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportresponse.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportresponse.md index 2c0b691c9d66e..3be800498a9b7 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportresponse.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportresponse.md @@ -20,4 +20,5 @@ export interface SavedObjectsImportResponse | [success](./kibana-plugin-core-public.savedobjectsimportresponse.success.md) | boolean | | | [successCount](./kibana-plugin-core-public.savedobjectsimportresponse.successcount.md) | number | | | [successResults](./kibana-plugin-core-public.savedobjectsimportresponse.successresults.md) | SavedObjectsImportSuccess[] | | +| [warnings](./kibana-plugin-core-public.savedobjectsimportresponse.warnings.md) | SavedObjectsImportWarning[] | | diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportresponse.warnings.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportresponse.warnings.md new file mode 100644 index 0000000000000..2e55a2e30f9cb --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportresponse.warnings.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsImportResponse](./kibana-plugin-core-public.savedobjectsimportresponse.md) > [warnings](./kibana-plugin-core-public.savedobjectsimportresponse.warnings.md) + +## SavedObjectsImportResponse.warnings property + +Signature: + +```typescript +warnings: SavedObjectsImportWarning[]; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.md new file mode 100644 index 0000000000000..4d6d984777c80 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsImportSimpleWarning](./kibana-plugin-core-public.savedobjectsimportsimplewarning.md) + +## SavedObjectsImportSimpleWarning interface + +A simple informative warning that will be displayed to the user. + +Signature: + +```typescript +export interface SavedObjectsImportSimpleWarning +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [message](./kibana-plugin-core-public.savedobjectsimportsimplewarning.message.md) | string | The translated message to display to the user | +| [type](./kibana-plugin-core-public.savedobjectsimportsimplewarning.type.md) | 'simple' | | + diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.message.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.message.md new file mode 100644 index 0000000000000..42c94e14e3d28 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.message.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsImportSimpleWarning](./kibana-plugin-core-public.savedobjectsimportsimplewarning.md) > [message](./kibana-plugin-core-public.savedobjectsimportsimplewarning.message.md) + +## SavedObjectsImportSimpleWarning.message property + +The translated message to display to the user + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.type.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.type.md new file mode 100644 index 0000000000000..86a4cbfa434e7 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportsimplewarning.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsImportSimpleWarning](./kibana-plugin-core-public.savedobjectsimportsimplewarning.md) > [type](./kibana-plugin-core-public.savedobjectsimportsimplewarning.type.md) + +## SavedObjectsImportSimpleWarning.type property + +Signature: + +```typescript +type: 'simple'; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportwarning.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportwarning.md new file mode 100644 index 0000000000000..a9a9a70774970 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsimportwarning.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsImportWarning](./kibana-plugin-core-public.savedobjectsimportwarning.md) + +## SavedObjectsImportWarning type + +Composite type of all the possible types of import warnings. + +See [SavedObjectsImportSimpleWarning](./kibana-plugin-core-public.savedobjectsimportsimplewarning.md) and [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-public.savedobjectsimportactionrequiredwarning.md) for more details. + +Signature: + +```typescript +export declare type SavedObjectsImportWarning = SavedObjectsImportSimpleWarning | SavedObjectsImportActionRequiredWarning; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 06c7983f89a78..7daf5d086d9e4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -168,13 +168,16 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsFindOptionsReference](./kibana-plugin-core-server.savedobjectsfindoptionsreference.md) | | | [SavedObjectsFindResponse](./kibana-plugin-core-server.savedobjectsfindresponse.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | | [SavedObjectsFindResult](./kibana-plugin-core-server.savedobjectsfindresult.md) | | +| [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) | A warning meant to notify that a specific user action is required to finalize the import of some type of object. The actionUrl must be a path relative to the basePath, and not include it. | | [SavedObjectsImportAmbiguousConflictError](./kibana-plugin-core-server.savedobjectsimportambiguousconflicterror.md) | Represents a failure to import due to a conflict, which can be resolved in different ways with an overwrite. | | [SavedObjectsImportConflictError](./kibana-plugin-core-server.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | | [SavedObjectsImportFailure](./kibana-plugin-core-server.savedobjectsimportfailure.md) | Represents a failure to import. | +| [SavedObjectsImportHookResult](./kibana-plugin-core-server.savedobjectsimporthookresult.md) | Result from a [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) | | [SavedObjectsImportMissingReferencesError](./kibana-plugin-core-server.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | | [SavedObjectsImportOptions](./kibana-plugin-core-server.savedobjectsimportoptions.md) | Options to control the import operation. | | [SavedObjectsImportResponse](./kibana-plugin-core-server.savedobjectsimportresponse.md) | The response describing the result of an import. | | [SavedObjectsImportRetry](./kibana-plugin-core-server.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | +| [SavedObjectsImportSimpleWarning](./kibana-plugin-core-server.savedobjectsimportsimplewarning.md) | A simple informative warning that will be displayed to the user. | | [SavedObjectsImportSuccess](./kibana-plugin-core-server.savedobjectsimportsuccess.md) | Represents a successful import. | | [SavedObjectsImportUnknownError](./kibana-plugin-core-server.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | | [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-core-server.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | @@ -295,6 +298,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientFactoryProvider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md). | | [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | | [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation | +| [SavedObjectsImportHook](./kibana-plugin-core-server.savedobjectsimporthook.md) | A hook associated with a specific saved object type, that will be invoked during the import process. The hook will have access to the objects of the registered type.Currently, the only supported feature for import hooks is to return warnings to be displayed in the UI when the import succeeds. The only interactions the hook can have with the import process is via the hook's response. Mutating the objects inside the hook's code will have no effect. | +| [SavedObjectsImportWarning](./kibana-plugin-core-server.savedobjectsimportwarning.md) | Composite type of all the possible types of import warnings.See [SavedObjectsImportSimpleWarning](./kibana-plugin-core-server.savedobjectsimportsimplewarning.md) and [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) for more details. | | [SavedObjectsNamespaceType](./kibana-plugin-core-server.savedobjectsnamespacetype.md) | The namespace type dictates how a saved object can be interacted in relation to namespaces. Each type is mutually exclusive: \* single (default): this type of saved object is namespace-isolated, e.g., it exists in only one namespace. \* multiple: this type of saved object is shareable, e.g., it can exist in one or more namespaces. \* agnostic: this type of saved object is global. | | [SavedObjectUnsanitizedDoc](./kibana-plugin-core-server.savedobjectunsanitizeddoc.md) | Describes Saved Object documents from Kibana < 7.0.0 which don't have a references root property defined. This type should only be used in migrations. | | [ScopeableRequest](./kibana-plugin-core-server.scopeablerequest.md) | A user credentials container. It accommodates the necessary auth credentials to impersonate the current user.See [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md). | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md new file mode 100644 index 0000000000000..4ec70301d2ebe --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) > [actionPath](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md) + +## SavedObjectsImportActionRequiredWarning.actionPath property + +The path (without the basePath) that the user should be redirect to to address this warning. + +Signature: + +```typescript +actionPath: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.buttonlabel.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.buttonlabel.md new file mode 100644 index 0000000000000..7fb5d53c487af --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.buttonlabel.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) > [buttonLabel](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.buttonlabel.md) + +## SavedObjectsImportActionRequiredWarning.buttonLabel property + +An optional label to use for the link button. If unspecified, a default label will be used. + +Signature: + +```typescript +buttonLabel?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md new file mode 100644 index 0000000000000..ba1e905344af1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) + +## SavedObjectsImportActionRequiredWarning interface + +A warning meant to notify that a specific user action is required to finalize the import of some type of object. + + The `actionUrl` must be a path relative to the basePath, and not include it. + +Signature: + +```typescript +export interface SavedObjectsImportActionRequiredWarning +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [actionPath](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.actionpath.md) | string | The path (without the basePath) that the user should be redirect to to address this warning. | +| [buttonLabel](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.buttonlabel.md) | string | An optional label to use for the link button. If unspecified, a default label will be used. | +| [message](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.message.md) | string | The translated message to display to the user. | +| [type](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.type.md) | 'action_required' | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.message.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.message.md new file mode 100644 index 0000000000000..1ab9afd4bad99 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.message.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) > [message](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.message.md) + +## SavedObjectsImportActionRequiredWarning.message property + +The translated message to display to the user. + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.type.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.type.md new file mode 100644 index 0000000000000..d8f22ef17d8f0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) > [type](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.type.md) + +## SavedObjectsImportActionRequiredWarning.type property + +Signature: + +```typescript +type: 'action_required'; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthook.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthook.md new file mode 100644 index 0000000000000..8d50ef94577de --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthook.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportHook](./kibana-plugin-core-server.savedobjectsimporthook.md) + +## SavedObjectsImportHook type + +A hook associated with a specific saved object type, that will be invoked during the import process. The hook will have access to the objects of the registered type. + +Currently, the only supported feature for import hooks is to return warnings to be displayed in the UI when the import succeeds. + + The only interactions the hook can have with the import process is via the hook's response. Mutating the objects inside the hook's code will have no effect. + +Signature: + +```typescript +export declare type SavedObjectsImportHook = (objects: Array>) => SavedObjectsImportHookResult | Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthookresult.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthookresult.md new file mode 100644 index 0000000000000..9756ce7fac350 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthookresult.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportHookResult](./kibana-plugin-core-server.savedobjectsimporthookresult.md) + +## SavedObjectsImportHookResult interface + +Result from a [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) + +Signature: + +```typescript +export interface SavedObjectsImportHookResult +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [warnings](./kibana-plugin-core-server.savedobjectsimporthookresult.warnings.md) | SavedObjectsImportWarning[] | An optional list of warnings to display in the UI when the import succeeds. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthookresult.warnings.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthookresult.warnings.md new file mode 100644 index 0000000000000..682b384f8d363 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimporthookresult.warnings.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportHookResult](./kibana-plugin-core-server.savedobjectsimporthookresult.md) > [warnings](./kibana-plugin-core-server.savedobjectsimporthookresult.warnings.md) + +## SavedObjectsImportHookResult.warnings property + +An optional list of warnings to display in the UI when the import succeeds. + +Signature: + +```typescript +warnings?: SavedObjectsImportWarning[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportresponse.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportresponse.md index 94d24e946b5bd..55f651197490f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportresponse.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportresponse.md @@ -20,4 +20,5 @@ export interface SavedObjectsImportResponse | [success](./kibana-plugin-core-server.savedobjectsimportresponse.success.md) | boolean | | | [successCount](./kibana-plugin-core-server.savedobjectsimportresponse.successcount.md) | number | | | [successResults](./kibana-plugin-core-server.savedobjectsimportresponse.successresults.md) | SavedObjectsImportSuccess[] | | +| [warnings](./kibana-plugin-core-server.savedobjectsimportresponse.warnings.md) | SavedObjectsImportWarning[] | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportresponse.warnings.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportresponse.warnings.md new file mode 100644 index 0000000000000..88cccf7f527e7 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportresponse.warnings.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportResponse](./kibana-plugin-core-server.savedobjectsimportresponse.md) > [warnings](./kibana-plugin-core-server.savedobjectsimportresponse.warnings.md) + +## SavedObjectsImportResponse.warnings property + +Signature: + +```typescript +warnings: SavedObjectsImportWarning[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.md new file mode 100644 index 0000000000000..52d46e4f8db80 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportSimpleWarning](./kibana-plugin-core-server.savedobjectsimportsimplewarning.md) + +## SavedObjectsImportSimpleWarning interface + +A simple informative warning that will be displayed to the user. + +Signature: + +```typescript +export interface SavedObjectsImportSimpleWarning +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [message](./kibana-plugin-core-server.savedobjectsimportsimplewarning.message.md) | string | The translated message to display to the user | +| [type](./kibana-plugin-core-server.savedobjectsimportsimplewarning.type.md) | 'simple' | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.message.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.message.md new file mode 100644 index 0000000000000..1e3ac7ec11365 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.message.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportSimpleWarning](./kibana-plugin-core-server.savedobjectsimportsimplewarning.md) > [message](./kibana-plugin-core-server.savedobjectsimportsimplewarning.message.md) + +## SavedObjectsImportSimpleWarning.message property + +The translated message to display to the user + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.type.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.type.md new file mode 100644 index 0000000000000..660b1b39d6c39 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportsimplewarning.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportSimpleWarning](./kibana-plugin-core-server.savedobjectsimportsimplewarning.md) > [type](./kibana-plugin-core-server.savedobjectsimportsimplewarning.type.md) + +## SavedObjectsImportSimpleWarning.type property + +Signature: + +```typescript +type: 'simple'; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportwarning.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportwarning.md new file mode 100644 index 0000000000000..257751f16601d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsimportwarning.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsImportWarning](./kibana-plugin-core-server.savedobjectsimportwarning.md) + +## SavedObjectsImportWarning type + +Composite type of all the possible types of import warnings. + +See [SavedObjectsImportSimpleWarning](./kibana-plugin-core-server.savedobjectsimportsimplewarning.md) and [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) for more details. + +Signature: + +```typescript +export declare type SavedObjectsImportWarning = SavedObjectsImportSimpleWarning | SavedObjectsImportActionRequiredWarning; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md index 9d87e51767caa..92b6ddf29b8ec 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md @@ -22,4 +22,5 @@ export interface SavedObjectsTypeManagementDefinition | [getTitle](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md) | (savedObject: SavedObject<any>) => string | Function returning the title to display in the management table. If not defined, will use the object's type and id to generate a label. | | [icon](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.icon.md) | string | The eui icon name to display in the management table. If not defined, the default icon will be used. | | [importableAndExportable](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.importableandexportable.md) | boolean | Is the type importable or exportable. Defaults to false. | +| [onImport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md) | SavedObjectsImportHook | An optional [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) to use when importing given type.Import hooks are executed during the savedObjects import process and allow to interact with the imported objects. See the [hook documentation](./kibana-plugin-core-server.savedobjectsimporthook.md) for more info. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md new file mode 100644 index 0000000000000..55733ca5d4443 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md @@ -0,0 +1,52 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsTypeManagementDefinition](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) > [onImport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md) + +## SavedObjectsTypeManagementDefinition.onImport property + +An optional [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) to use when importing given type. + +Import hooks are executed during the savedObjects import process and allow to interact with the imported objects. See the [hook documentation](./kibana-plugin-core-server.savedobjectsimporthook.md) for more info. + +Signature: + +```typescript +onImport?: SavedObjectsImportHook; +``` + +## Example + +Registering a hook displaying a warning about a specific type of object + +```ts +// src/plugins/my_plugin/server/plugin.ts +import { myType } from './saved_objects'; + +export class Plugin() { + setup: (core: CoreSetup) => { + core.savedObjects.registerType({ + ...myType, + management: { + ...myType.management, + onImport: (objects) => { + if(someActionIsNeeded(objects)) { + return { + warnings: [ + { + type: 'action_required', + message: 'Objects need to be manually enabled after import', + actionPath: '/app/my-app/require-activation', + }, + ] + } + } + return {}; + } + }, + }); + } +} + +``` + messages returned in the warnings are user facing and must be translated. + diff --git a/src/core/public/http/base_path.mock.ts b/src/core/public/http/base_path.mock.ts new file mode 100644 index 0000000000000..851ad1ffca440 --- /dev/null +++ b/src/core/public/http/base_path.mock.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { IBasePath } from './types'; + +const createBasePathMock = ({ + publicBaseUrl = '/', + serverBasePath = '/', +}: { publicBaseUrl?: string; serverBasePath?: string } = {}) => { + const mock: jest.Mocked = { + prepend: jest.fn(), + get: jest.fn(), + remove: jest.fn(), + publicBaseUrl, + serverBasePath, + }; + + return mock; +}; + +export const basePathMock = { + create: createBasePathMock, +}; diff --git a/src/core/public/http/http_service.mock.ts b/src/core/public/http/http_service.mock.ts index bbc412461c480..c00773b510556 100644 --- a/src/core/public/http/http_service.mock.ts +++ b/src/core/public/http/http_service.mock.ts @@ -11,6 +11,7 @@ import { HttpService } from './http_service'; import { HttpSetup } from './types'; import { BehaviorSubject } from 'rxjs'; import { BasePath } from './base_path'; +import { basePathMock } from './base_path.mock'; export type HttpSetupMock = jest.Mocked & { basePath: BasePath; @@ -54,4 +55,5 @@ export const httpServiceMock = { create: createMock, createSetupContract: createServiceMock, createStartContract: createServiceMock, + createBasePath: basePathMock.create, }; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 784def847f1ba..66dd4f3028aaf 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -130,6 +130,9 @@ export { SavedObjectsImportFailure, SavedObjectsImportRetry, SavedObjectsNamespaceType, + SavedObjectsImportSimpleWarning, + SavedObjectsImportActionRequiredWarning, + SavedObjectsImportWarning, } from './saved_objects'; export { diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 5c0b2a45abd46..da818470133cd 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1198,6 +1198,15 @@ export interface SavedObjectsFindResponsePublic extends SavedObject total: number; } +// @public +export interface SavedObjectsImportActionRequiredWarning { + actionPath: string; + buttonLabel?: string; + message: string; + // (undocumented) + type: 'action_required'; +} + // @public export interface SavedObjectsImportAmbiguousConflictError { // (undocumented) @@ -1257,6 +1266,8 @@ export interface SavedObjectsImportResponse { successCount: number; // (undocumented) successResults?: SavedObjectsImportSuccess[]; + // (undocumented) + warnings: SavedObjectsImportWarning[]; } // @public @@ -1278,6 +1289,13 @@ export interface SavedObjectsImportRetry { type: string; } +// @public +export interface SavedObjectsImportSimpleWarning { + message: string; + // (undocumented) + type: 'simple'; +} + // @public export interface SavedObjectsImportSuccess { // @deprecated (undocumented) @@ -1311,6 +1329,9 @@ export interface SavedObjectsImportUnsupportedTypeError { type: 'unsupported_type'; } +// @public +export type SavedObjectsImportWarning = SavedObjectsImportSimpleWarning | SavedObjectsImportActionRequiredWarning; + // @public export interface SavedObjectsMigrationVersion { // (undocumented) diff --git a/src/core/public/saved_objects/index.ts b/src/core/public/saved_objects/index.ts index e83d2044a2d70..537ca0da088ff 100644 --- a/src/core/public/saved_objects/index.ts +++ b/src/core/public/saved_objects/index.ts @@ -35,6 +35,9 @@ export { SavedObjectsImportFailure, SavedObjectsImportRetry, SavedObjectsNamespaceType, + SavedObjectsImportSimpleWarning, + SavedObjectsImportActionRequiredWarning, + SavedObjectsImportWarning, } from '../../server/types'; export { diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 187c2afd17039..0eb246b4c978b 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -321,6 +321,11 @@ export { SavedObjectsImporter, ISavedObjectsImporter, SavedObjectsImportError, + SavedObjectsImportHook, + SavedObjectsImportHookResult, + SavedObjectsImportSimpleWarning, + SavedObjectsImportActionRequiredWarning, + SavedObjectsImportWarning, } from './saved_objects'; export { diff --git a/src/core/server/saved_objects/import/import_saved_objects.test.ts b/src/core/server/saved_objects/import/import_saved_objects.test.ts index e675fb7ea1000..11d3df5faae53 100644 --- a/src/core/server/saved_objects/import/import_saved_objects.test.ts +++ b/src/core/server/saved_objects/import/import_saved_objects.test.ts @@ -18,6 +18,7 @@ import { savedObjectsClientMock } from '../../mocks'; import { ISavedObjectTypeRegistry } from '..'; import { typeRegistryMock } from '../saved_objects_type_registry.mock'; import { importSavedObjectsFromStream, ImportSavedObjectsOptions } from './import_saved_objects'; +import { SavedObjectsImportHook, SavedObjectsImportWarning } from './types'; import { collectSavedObjects, @@ -26,6 +27,7 @@ import { checkConflicts, checkOriginConflicts, createSavedObjects, + executeImportHooks, } from './lib'; jest.mock('./lib/collect_saved_objects'); @@ -34,6 +36,7 @@ jest.mock('./lib/validate_references'); jest.mock('./lib/check_conflicts'); jest.mock('./lib/check_origin_conflicts'); jest.mock('./lib/create_saved_objects'); +jest.mock('./lib/execute_import_hooks'); const getMockFn = any, U>(fn: (...args: Parameters) => U) => fn as jest.MockedFunction<(...args: Parameters) => U>; @@ -61,6 +64,7 @@ describe('#importSavedObjectsFromStream', () => { pendingOverwrites: new Set(), }); getMockFn(createSavedObjects).mockResolvedValue({ errors: [], createdObjects: [] }); + getMockFn(executeImportHooks).mockResolvedValue([]); }); let readStream: Readable; @@ -70,14 +74,19 @@ describe('#importSavedObjectsFromStream', () => { let typeRegistry: jest.Mocked; const namespace = 'some-namespace'; - const setupOptions = ( - createNewCopies: boolean = false, - getTypeImpl: (name: string) => any = (type: string) => + const setupOptions = ({ + createNewCopies = false, + getTypeImpl = (type: string) => ({ // other attributes aren't needed for the purposes of injecting metadata management: { icon: `${type}-icon` }, - } as any) - ): ImportSavedObjectsOptions => { + } as any), + importHooks = {}, + }: { + createNewCopies?: boolean; + getTypeImpl?: (name: string) => any; + importHooks?: Record; + } = {}): ImportSavedObjectsOptions => { readStream = new Readable(); savedObjectsClient = savedObjectsClientMock.create(); typeRegistry = typeRegistryMock.create(); @@ -90,6 +99,7 @@ describe('#importSavedObjectsFromStream', () => { typeRegistry, namespace, createNewCopies, + importHooks, }; }; const createObject = ({ @@ -153,6 +163,31 @@ describe('#importSavedObjectsFromStream', () => { ); }); + test('executes import hooks', async () => { + const importHooks = { + foo: [jest.fn()], + }; + + const options = setupOptions({ importHooks }); + const collectedObjects = [createObject()]; + getMockFn(collectSavedObjects).mockResolvedValue({ + errors: [], + collectedObjects, + importIdMap: new Map(), + }); + getMockFn(createSavedObjects).mockResolvedValue({ + errors: [], + createdObjects: collectedObjects, + }); + + await importSavedObjectsFromStream(options); + + expect(executeImportHooks).toHaveBeenCalledWith({ + objects: collectedObjects, + importHooks, + }); + }); + describe('with createNewCopies disabled', () => { test('does not regenerate object IDs', async () => { const options = setupOptions(); @@ -256,7 +291,7 @@ describe('#importSavedObjectsFromStream', () => { describe('with createNewCopies enabled', () => { test('regenerates object IDs', async () => { - const options = setupOptions(true); + const options = setupOptions({ createNewCopies: true }); const collectedObjects = [createObject()]; getMockFn(collectSavedObjects).mockResolvedValue({ errors: [], @@ -269,7 +304,7 @@ describe('#importSavedObjectsFromStream', () => { }); test('does not check conflicts or check origin conflicts', async () => { - const options = setupOptions(true); + const options = setupOptions({ createNewCopies: true }); getMockFn(validateReferences).mockResolvedValue([]); await importSavedObjectsFromStream(options); @@ -278,7 +313,7 @@ describe('#importSavedObjectsFromStream', () => { }); test('creates saved objects', async () => { - const options = setupOptions(true); + const options = setupOptions({ createNewCopies: true }); const collectedObjects = [createObject()]; const errors = [createError(), createError()]; getMockFn(collectSavedObjects).mockResolvedValue({ @@ -313,7 +348,7 @@ describe('#importSavedObjectsFromStream', () => { const options = setupOptions(); const result = await importSavedObjectsFromStream(options); - expect(result).toEqual({ success: true, successCount: 0 }); + expect(result).toEqual({ success: true, successCount: 0, warnings: [] }); }); test('returns success=false if an error occurred', async () => { @@ -325,7 +360,33 @@ describe('#importSavedObjectsFromStream', () => { }); const result = await importSavedObjectsFromStream(options); - expect(result).toEqual({ success: false, successCount: 0, errors: [expect.any(Object)] }); + expect(result).toEqual({ + success: false, + successCount: 0, + errors: [expect.any(Object)], + warnings: [], + }); + }); + + test('returns warnings from the import hooks', async () => { + const options = setupOptions(); + const collectedObjects = [createObject()]; + getMockFn(collectSavedObjects).mockResolvedValue({ + errors: [], + collectedObjects, + importIdMap: new Map(), + }); + getMockFn(createSavedObjects).mockResolvedValue({ + errors: [], + createdObjects: collectedObjects, + }); + + const warnings: SavedObjectsImportWarning[] = [{ type: 'simple', message: 'foo' }]; + getMockFn(executeImportHooks).mockResolvedValue(warnings); + + const result = await importSavedObjectsFromStream(options); + + expect(result.warnings).toEqual(warnings); }); describe('handles a mix of successes and errors and injects metadata', () => { @@ -389,12 +450,13 @@ describe('#importSavedObjectsFromStream', () => { successCount: 3, successResults, errors: errorResults, + warnings: [], }); }); test('with createNewCopies enabled', async () => { // however, we include it here for posterity - const options = setupOptions(true); + const options = setupOptions({ createNewCopies: true }); getMockFn(createSavedObjects).mockResolvedValue({ errors, createdObjects }); const result = await importSavedObjectsFromStream(options); @@ -410,6 +472,7 @@ describe('#importSavedObjectsFromStream', () => { successCount: 3, successResults, errors: errorResults, + warnings: [], }); }); }); @@ -418,15 +481,18 @@ describe('#importSavedObjectsFromStream', () => { const obj1 = createObject({ type: 'foo' }); const obj2 = createObject({ type: 'bar', title: 'bar-title' }); - const options = setupOptions(false, (type) => { - if (type === 'foo') { + const options = setupOptions({ + createNewCopies: false, + getTypeImpl: (type) => { + if (type === 'foo') { + return { + management: { getTitle: () => 'getTitle-foo', icon: `${type}-icon` }, + }; + } return { - management: { getTitle: () => 'getTitle-foo', icon: `${type}-icon` }, + management: { icon: `${type}-icon` }, }; - } - return { - management: { icon: `${type}-icon` }, - }; + }, }); getMockFn(checkConflicts).mockResolvedValue({ @@ -456,6 +522,7 @@ describe('#importSavedObjectsFromStream', () => { success: true, successCount: 2, successResults, + warnings: [], }); }); @@ -483,7 +550,12 @@ describe('#importSavedObjectsFromStream', () => { const result = await importSavedObjectsFromStream(options); const expectedErrors = errors.map(({ type, id }) => expect.objectContaining({ type, id })); - expect(result).toEqual({ success: false, successCount: 0, errors: expectedErrors }); + expect(result).toEqual({ + success: false, + successCount: 0, + errors: expectedErrors, + warnings: [], + }); }); }); }); diff --git a/src/core/server/saved_objects/import/import_saved_objects.ts b/src/core/server/saved_objects/import/import_saved_objects.ts index a788bcf47d321..9baef59dc162a 100644 --- a/src/core/server/saved_objects/import/import_saved_objects.ts +++ b/src/core/server/saved_objects/import/import_saved_objects.ts @@ -9,7 +9,11 @@ import { Readable } from 'stream'; import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry'; import { SavedObjectsClientContract } from '../types'; -import { SavedObjectsImportFailure, SavedObjectsImportResponse } from './types'; +import { + SavedObjectsImportFailure, + SavedObjectsImportResponse, + SavedObjectsImportHook, +} from './types'; import { validateReferences, checkOriginConflicts, @@ -17,6 +21,7 @@ import { checkConflicts, regenerateIds, collectSavedObjects, + executeImportHooks, } from './lib'; /** @@ -33,6 +38,8 @@ export interface ImportSavedObjectsOptions { savedObjectsClient: SavedObjectsClientContract; /** The registry of all known saved object types */ typeRegistry: ISavedObjectTypeRegistry; + /** List of registered import hooks */ + importHooks: Record; /** if specified, will import in given namespace, else will import as global object */ namespace?: string; /** If true, will create new copies of import objects, each with a random `id` and undefined `originId`. */ @@ -52,6 +59,7 @@ export async function importSavedObjectsFromStream({ createNewCopies, savedObjectsClient, typeRegistry, + importHooks, namespace, }: ImportSavedObjectsOptions): Promise { let errorAccumulator: SavedObjectsImportFailure[] = []; @@ -147,10 +155,15 @@ export async function importSavedObjectsFromStream({ ...(attemptedOverwrite && { overwrite: true }), }; }); + const warnings = await executeImportHooks({ + objects: createSavedObjectsResult.createdObjects, + importHooks, + }); return { successCount: createSavedObjectsResult.createdObjects.length, success: errorAccumulator.length === 0, + warnings, ...(successResults.length && { successResults }), ...(errorResults.length && { errors: errorResults }), }; diff --git a/src/core/server/saved_objects/import/index.ts b/src/core/server/saved_objects/import/index.ts index 4cc2e6e83995b..0616c1277a3ac 100644 --- a/src/core/server/saved_objects/import/index.ts +++ b/src/core/server/saved_objects/import/index.ts @@ -19,5 +19,10 @@ export { SavedObjectsImportUnsupportedTypeError, SavedObjectsResolveImportErrorsOptions, SavedObjectsImportRetry, + SavedObjectsImportHook, + SavedObjectsImportHookResult, + SavedObjectsImportSimpleWarning, + SavedObjectsImportActionRequiredWarning, + SavedObjectsImportWarning, } from './types'; export { SavedObjectsImportError } from './errors'; diff --git a/src/core/server/saved_objects/import/lib/execute_import_hooks.test.ts b/src/core/server/saved_objects/import/lib/execute_import_hooks.test.ts new file mode 100644 index 0000000000000..ca769bc9ac4c1 --- /dev/null +++ b/src/core/server/saved_objects/import/lib/execute_import_hooks.test.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { SavedObject } from '../../types'; +import { SavedObjectsImportHookResult, SavedObjectsImportWarning } from '../types'; +import { executeImportHooks } from './execute_import_hooks'; + +const createObject = (type: string, id: string): SavedObject => ({ + type, + id, + attributes: {}, + references: [], +}); + +const createHook = ( + result: SavedObjectsImportHookResult | Promise = {} +) => jest.fn().mockReturnValue(result); + +const createWarning = (message: string): SavedObjectsImportWarning => ({ + type: 'simple', + message, +}); + +describe('executeImportHooks', () => { + it('invokes the hooks with the correct objects', async () => { + const foo1 = createObject('foo', '1'); + const foo2 = createObject('foo', '2'); + const bar1 = createObject('bar', '1'); + const objects = [foo1, bar1, foo2]; + + const fooHook = createHook(); + const barHook = createHook(); + + await executeImportHooks({ + objects, + importHooks: { + foo: [fooHook], + bar: [barHook], + }, + }); + + expect(fooHook).toHaveBeenCalledTimes(1); + expect(fooHook).toHaveBeenCalledWith([foo1, foo2]); + + expect(barHook).toHaveBeenCalledTimes(1); + expect(barHook).toHaveBeenCalledWith([bar1]); + }); + + it('handles multiple hooks per type', async () => { + const foo1 = createObject('foo', '1'); + const foo2 = createObject('foo', '2'); + const bar1 = createObject('bar', '1'); + const objects = [foo1, bar1, foo2]; + + const fooHook1 = createHook(); + const fooHook2 = createHook(); + + await executeImportHooks({ + objects, + importHooks: { + foo: [fooHook1, fooHook2], + }, + }); + + expect(fooHook1).toHaveBeenCalledTimes(1); + expect(fooHook1).toHaveBeenCalledWith([foo1, foo2]); + + expect(fooHook2).toHaveBeenCalledTimes(1); + expect(fooHook2).toHaveBeenCalledWith([foo1, foo2]); + }); + + it('does not call a hook if no object of its type is present', async () => { + const objects = [createObject('foo', '1'), createObject('foo', '2')]; + const hook = createHook(); + + await executeImportHooks({ + objects, + importHooks: { + bar: [hook], + }, + }); + + expect(hook).not.toHaveBeenCalled(); + }); + + it('returns the warnings returned by the hooks', async () => { + const foo1 = createObject('foo', '1'); + const bar1 = createObject('bar', '1'); + const objects = [foo1, bar1]; + + const fooWarning1 = createWarning('foo warning 1'); + const fooWarning2 = createWarning('foo warning 2'); + const barWarning = createWarning('bar warning'); + + const fooHook = createHook({ warnings: [fooWarning1, fooWarning2] }); + const barHook = createHook({ warnings: [barWarning] }); + + const warnings = await executeImportHooks({ + objects, + importHooks: { + foo: [fooHook], + bar: [barHook], + }, + }); + + expect(warnings).toEqual([fooWarning1, fooWarning2, barWarning]); + }); + + it('handles asynchronous hooks', async () => { + const foo1 = createObject('foo', '1'); + const bar1 = createObject('bar', '1'); + const objects = [foo1, bar1]; + + const fooWarning = createWarning('foo warning 1'); + const barWarning = createWarning('bar warning'); + + const fooHook = createHook(Promise.resolve({ warnings: [fooWarning] })); + const barHook = createHook(Promise.resolve({ warnings: [barWarning] })); + + const warnings = await executeImportHooks({ + objects, + importHooks: { + foo: [fooHook], + bar: [barHook], + }, + }); + + expect(warnings).toEqual([fooWarning, barWarning]); + }); +}); diff --git a/src/core/server/saved_objects/import/lib/execute_import_hooks.ts b/src/core/server/saved_objects/import/lib/execute_import_hooks.ts new file mode 100644 index 0000000000000..aff8bac1c17ca --- /dev/null +++ b/src/core/server/saved_objects/import/lib/execute_import_hooks.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { SavedObject } from '../../types'; +import { SavedObjectsImportHook, SavedObjectsImportWarning } from '../types'; + +interface ExecuteImportHooksOptions { + objects: SavedObject[]; + importHooks: Record; +} + +export const executeImportHooks = async ({ + objects, + importHooks, +}: ExecuteImportHooksOptions): Promise => { + const objsByType = splitByType(objects); + let warnings: SavedObjectsImportWarning[] = []; + + for (const [type, typeObjs] of Object.entries(objsByType)) { + const hooks = importHooks[type] ?? []; + for (const hook of hooks) { + const hookResult = await hook(typeObjs); + if (hookResult.warnings) { + warnings = [...warnings, ...hookResult.warnings]; + } + } + } + + return warnings; +}; + +const splitByType = (objects: SavedObject[]): Record => { + return objects.reduce((memo, obj) => { + memo[obj.type] = [...(memo[obj.type] ?? []), obj]; + return memo; + }, {} as Record); +}; diff --git a/src/core/server/saved_objects/import/lib/index.ts b/src/core/server/saved_objects/import/lib/index.ts index ceb301cd10181..64735f1d0daca 100644 --- a/src/core/server/saved_objects/import/lib/index.ts +++ b/src/core/server/saved_objects/import/lib/index.ts @@ -18,3 +18,4 @@ export { regenerateIds } from './regenerate_ids'; export { splitOverwrites } from './split_overwrites'; export { getNonExistingReferenceAsKeys, validateReferences } from './validate_references'; export { validateRetries } from './validate_retries'; +export { executeImportHooks } from './execute_import_hooks'; diff --git a/src/core/server/saved_objects/import/resolve_import_errors.test.ts b/src/core/server/saved_objects/import/resolve_import_errors.test.ts index 28b31d22a4de8..b4861a35266b3 100644 --- a/src/core/server/saved_objects/import/resolve_import_errors.test.ts +++ b/src/core/server/saved_objects/import/resolve_import_errors.test.ts @@ -15,9 +15,10 @@ import { SavedObjectsImportFailure, SavedObjectsImportRetry, SavedObjectReference, + SavedObjectsImportWarning, } from '../types'; import { savedObjectsClientMock } from '../../mocks'; -import { ISavedObjectTypeRegistry } from '..'; +import { ISavedObjectTypeRegistry, SavedObjectsImportHook } from '..'; import { typeRegistryMock } from '../saved_objects_type_registry.mock'; import { resolveSavedObjectsImportErrors, @@ -34,6 +35,7 @@ import { splitOverwrites, createSavedObjects, createObjectsFilter, + executeImportHooks, } from './lib'; jest.mock('./lib/validate_retries'); @@ -45,6 +47,7 @@ jest.mock('./lib/check_conflicts'); jest.mock('./lib/check_origin_conflicts'); jest.mock('./lib/split_overwrites'); jest.mock('./lib/create_saved_objects'); +jest.mock('./lib/execute_import_hooks'); const getMockFn = any, U>(fn: (...args: Parameters) => U) => fn as jest.MockedFunction<(...args: Parameters) => U>; @@ -73,6 +76,7 @@ describe('#importSavedObjectsFromStream', () => { objectsToNotOverwrite: [], }); getMockFn(createSavedObjects).mockResolvedValue({ errors: [], createdObjects: [] }); + getMockFn(executeImportHooks).mockResolvedValue([]); }); let readStream: Readable; @@ -81,15 +85,21 @@ describe('#importSavedObjectsFromStream', () => { let typeRegistry: jest.Mocked; const namespace = 'some-namespace'; - const setupOptions = ( - retries: SavedObjectsImportRetry[] = [], - createNewCopies: boolean = false, - getTypeImpl: (name: string) => any = (type: string) => + const setupOptions = ({ + retries = [], + createNewCopies = false, + getTypeImpl = (type: string) => ({ // other attributes aren't needed for the purposes of injecting metadata management: { icon: `${type}-icon` }, - } as any) - ): ResolveSavedObjectsImportErrorsOptions => { + } as any), + importHooks = {}, + }: { + retries?: SavedObjectsImportRetry[]; + createNewCopies?: boolean; + getTypeImpl?: (name: string) => any; + importHooks?: Record; + } = {}): ResolveSavedObjectsImportErrorsOptions => { readStream = new Readable(); savedObjectsClient = savedObjectsClientMock.create(); typeRegistry = typeRegistryMock.create(); @@ -101,6 +111,7 @@ describe('#importSavedObjectsFromStream', () => { retries, savedObjectsClient, typeRegistry, + importHooks, // namespace and createNewCopies don't matter, as they don't change the logic in this module, they just get passed to sub-module methods namespace, createNewCopies, @@ -148,7 +159,7 @@ describe('#importSavedObjectsFromStream', () => { describe('module calls', () => { test('validates retries', async () => { const retry = createRetry(); - const options = setupOptions([retry]); + const options = setupOptions({ retries: [retry] }); await resolveSavedObjectsImportErrors(options); expect(validateRetries).toHaveBeenCalledWith([retry]); @@ -156,7 +167,7 @@ describe('#importSavedObjectsFromStream', () => { test('creates objects filter', async () => { const retry = createRetry(); - const options = setupOptions([retry]); + const options = setupOptions({ retries: [retry] }); await resolveSavedObjectsImportErrors(options); expect(createObjectsFilter).toHaveBeenCalledWith([retry]); @@ -178,7 +189,7 @@ describe('#importSavedObjectsFromStream', () => { test('validates references', async () => { const retries = [createRetry()]; - const options = setupOptions(retries); + const options = setupOptions({ retries }); const collectedObjects = [createObject()]; getMockFn(collectSavedObjects).mockResolvedValue({ errors: [], @@ -195,6 +206,30 @@ describe('#importSavedObjectsFromStream', () => { ); }); + test('execute import hooks', async () => { + const importHooks = { + foo: [jest.fn()], + }; + const options = setupOptions({ importHooks }); + const collectedObjects = [createObject()]; + getMockFn(collectSavedObjects).mockResolvedValue({ + errors: [], + collectedObjects, + importIdMap: new Map(), + }); + getMockFn(createSavedObjects).mockResolvedValueOnce({ + errors: [], + createdObjects: collectedObjects, + }); + + await resolveSavedObjectsImportErrors(options); + + expect(executeImportHooks).toHaveBeenCalledWith({ + objects: collectedObjects, + importHooks, + }); + }); + test('uses `retries` to replace references of collected objects before validating', async () => { const object = createObject([{ type: 'bar-type', id: 'abc', name: 'some name' }]); const retries = [ @@ -203,7 +238,7 @@ describe('#importSavedObjectsFromStream', () => { replaceReferences: [{ type: 'bar-type', from: 'abc', to: 'def' }], }), ]; - const options = setupOptions(retries); + const options = setupOptions({ retries }); getMockFn(collectSavedObjects).mockResolvedValue({ errors: [], collectedObjects: [object], @@ -226,7 +261,7 @@ describe('#importSavedObjectsFromStream', () => { test('checks conflicts', async () => { const createNewCopies = (Symbol() as unknown) as boolean; const retries = [createRetry()]; - const options = setupOptions(retries, createNewCopies); + const options = setupOptions({ retries, createNewCopies }); const collectedObjects = [createObject()]; getMockFn(collectSavedObjects).mockResolvedValue({ errors: [], @@ -248,7 +283,7 @@ describe('#importSavedObjectsFromStream', () => { test('gets import ID map for retries', async () => { const retries = [createRetry()]; const createNewCopies = (Symbol() as unknown) as boolean; - const options = setupOptions(retries, createNewCopies); + const options = setupOptions({ retries, createNewCopies }); const filteredObjects = [createObject()]; getMockFn(checkConflicts).mockResolvedValue({ errors: [], @@ -264,7 +299,7 @@ describe('#importSavedObjectsFromStream', () => { test('splits objects to overwrite from those not to overwrite', async () => { const retries = [createRetry()]; - const options = setupOptions(retries); + const options = setupOptions({ retries }); const collectedObjects = [createObject()]; getMockFn(collectSavedObjects).mockResolvedValue({ errors: [], @@ -344,7 +379,7 @@ describe('#importSavedObjectsFromStream', () => { describe('with createNewCopies enabled', () => { test('regenerates object IDs', async () => { - const options = setupOptions([], true); + const options = setupOptions({ createNewCopies: true }); const collectedObjects = [createObject()]; getMockFn(collectSavedObjects).mockResolvedValue({ errors: [], @@ -357,7 +392,7 @@ describe('#importSavedObjectsFromStream', () => { }); test('creates saved objects', async () => { - const options = setupOptions([], true); + const options = setupOptions({ createNewCopies: true }); const errors = [createError(), createError(), createError()]; getMockFn(collectSavedObjects).mockResolvedValue({ errors: [errors[0]], @@ -422,7 +457,7 @@ describe('#importSavedObjectsFromStream', () => { const options = setupOptions(); const result = await resolveSavedObjectsImportErrors(options); - expect(result).toEqual({ success: true, successCount: 0 }); + expect(result).toEqual({ success: true, successCount: 0, warnings: [] }); }); test('returns success=false if an error occurred', async () => { @@ -434,15 +469,40 @@ describe('#importSavedObjectsFromStream', () => { }); const result = await resolveSavedObjectsImportErrors(options); - expect(result).toEqual({ success: false, successCount: 0, errors: [expect.any(Object)] }); + expect(result).toEqual({ + success: false, + successCount: 0, + errors: [expect.any(Object)], + warnings: [], + }); + }); + + test('executes import hooks', async () => { + const options = setupOptions(); + const collectedObjects = [createObject()]; + getMockFn(collectSavedObjects).mockResolvedValue({ + errors: [], + collectedObjects, + importIdMap: new Map(), + }); + getMockFn(createSavedObjects).mockResolvedValueOnce({ + errors: [], + createdObjects: collectedObjects, + }); + const warnings: SavedObjectsImportWarning[] = [{ type: 'simple', message: 'foo' }]; + getMockFn(executeImportHooks).mockResolvedValue(warnings); + + const result = await resolveSavedObjectsImportErrors(options); + + expect(result.warnings).toEqual(warnings); }); test('handles a mix of successes and errors and injects metadata', async () => { const error1 = createError(); const error2 = createError(); - const options = setupOptions([ - { type: error2.type, id: error2.id, overwrite: true, replaceReferences: [] }, - ]); + const options = setupOptions({ + retries: [{ type: error2.type, id: error2.id, overwrite: true, replaceReferences: [] }], + }); const obj1 = createObject(); const tmp = createObject(); const obj2 = { ...tmp, destinationId: 'some-destinationId', originId: tmp.id }; @@ -483,22 +543,30 @@ describe('#importSavedObjectsFromStream', () => { { ...error1, meta: { ...error1.meta, icon: `${error1.type}-icon` } }, { ...error2, meta: { ...error2.meta, icon: `${error2.type}-icon` }, overwrite: true }, ]; - expect(result).toEqual({ success: false, successCount: 3, successResults, errors }); + expect(result).toEqual({ + success: false, + successCount: 3, + successResults, + errors, + warnings: [], + }); }); test('uses `type.management.getTitle` to resolve the titles', async () => { const obj1 = createObject([], { type: 'foo' }); const obj2 = createObject([], { type: 'bar', title: 'bar-title' }); - const options = setupOptions([], false, (type) => { - if (type === 'foo') { + const options = setupOptions({ + getTypeImpl: (type) => { + if (type === 'foo') { + return { + management: { getTitle: () => 'getTitle-foo', icon: `${type}-icon` }, + }; + } return { - management: { getTitle: () => 'getTitle-foo', icon: `${type}-icon` }, + management: { icon: `${type}-icon` }, }; - } - return { - management: { icon: `${type}-icon` }, - }; + }, }); getMockFn(checkConflicts).mockResolvedValue({ @@ -532,6 +600,7 @@ describe('#importSavedObjectsFromStream', () => { success: true, successCount: 2, successResults, + warnings: [], }); }); @@ -555,7 +624,12 @@ describe('#importSavedObjectsFromStream', () => { const result = await resolveSavedObjectsImportErrors(options); const expectedErrors = errors.map(({ type, id }) => expect.objectContaining({ type, id })); - expect(result).toEqual({ success: false, successCount: 0, errors: expectedErrors }); + expect(result).toEqual({ + success: false, + successCount: 0, + errors: expectedErrors, + warnings: [], + }); }); }); }); diff --git a/src/core/server/saved_objects/import/resolve_import_errors.ts b/src/core/server/saved_objects/import/resolve_import_errors.ts index 5bb4c79e34cd4..4526eefe467d8 100644 --- a/src/core/server/saved_objects/import/resolve_import_errors.ts +++ b/src/core/server/saved_objects/import/resolve_import_errors.ts @@ -11,6 +11,7 @@ import { SavedObject, SavedObjectsClientContract, SavedObjectsImportRetry } from import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry'; import { SavedObjectsImportFailure, + SavedObjectsImportHook, SavedObjectsImportResponse, SavedObjectsImportSuccess, } from './types'; @@ -24,6 +25,7 @@ import { createSavedObjects, getImportIdMapForRetries, checkConflicts, + executeImportHooks, } from './lib'; /** @@ -38,6 +40,8 @@ export interface ResolveSavedObjectsImportErrorsOptions { savedObjectsClient: SavedObjectsClientContract; /** The registry of all known saved object types */ typeRegistry: ISavedObjectTypeRegistry; + /** List of registered import hooks */ + importHooks: Record; /** saved object import references to retry */ retries: SavedObjectsImportRetry[]; /** if specified, will import in given namespace */ @@ -58,6 +62,7 @@ export async function resolveSavedObjectsImportErrors({ retries, savedObjectsClient, typeRegistry, + importHooks, namespace, createNewCopies, }: ResolveSavedObjectsImportErrorsOptions): Promise { @@ -146,6 +151,7 @@ export async function resolveSavedObjectsImportErrors({ // Bulk create in two batches, overwrites and non-overwrites let successResults: SavedObjectsImportSuccess[] = []; + let successObjects: SavedObject[] = []; const accumulatedErrors = [...errorAccumulator]; const bulkCreateObjects = async ( objects: Array>, @@ -162,6 +168,7 @@ export async function resolveSavedObjectsImportErrors({ const { createdObjects, errors: bulkCreateErrors } = await createSavedObjects( createSavedObjectsParams ); + successObjects = [...successObjects, ...createdObjects]; errorAccumulator = [...errorAccumulator, ...bulkCreateErrors]; successCount += createdObjects.length; successResults = [ @@ -200,9 +207,15 @@ export async function resolveSavedObjectsImportErrors({ }; }); + const warnings = await executeImportHooks({ + objects: successObjects, + importHooks, + }); + return { successCount, success: errorAccumulator.length === 0, + warnings, ...(successResults.length && { successResults }), ...(errorResults.length && { errors: errorResults }), }; diff --git a/src/core/server/saved_objects/import/saved_objects_importer.ts b/src/core/server/saved_objects/import/saved_objects_importer.ts index 77f4afd519cca..94568cb336634 100644 --- a/src/core/server/saved_objects/import/saved_objects_importer.ts +++ b/src/core/server/saved_objects/import/saved_objects_importer.ts @@ -15,6 +15,7 @@ import { SavedObjectsImportResponse, SavedObjectsImportOptions, SavedObjectsResolveImportErrorsOptions, + SavedObjectsImportHook, } from './types'; /** @@ -29,6 +30,7 @@ export class SavedObjectsImporter { readonly #savedObjectsClient: SavedObjectsClientContract; readonly #typeRegistry: ISavedObjectTypeRegistry; readonly #importSizeLimit: number; + readonly #importHooks: Record; constructor({ savedObjectsClient, @@ -42,6 +44,15 @@ export class SavedObjectsImporter { this.#savedObjectsClient = savedObjectsClient; this.#typeRegistry = typeRegistry; this.#importSizeLimit = importSizeLimit; + this.#importHooks = typeRegistry.getAllTypes().reduce((hooks, type) => { + if (type.management?.onImport) { + return { + ...hooks, + [type.name]: [type.management.onImport], + }; + } + return hooks; + }, {} as Record); } /** @@ -64,6 +75,7 @@ export class SavedObjectsImporter { objectLimit: this.#importSizeLimit, savedObjectsClient: this.#savedObjectsClient, typeRegistry: this.#typeRegistry, + importHooks: this.#importHooks, }); } @@ -87,6 +99,7 @@ export class SavedObjectsImporter { objectLimit: this.#importSizeLimit, savedObjectsClient: this.#savedObjectsClient, typeRegistry: this.#typeRegistry, + importHooks: this.#importHooks, }); } } diff --git a/src/core/server/saved_objects/import/types.ts b/src/core/server/saved_objects/import/types.ts index 8a159824ec7a4..bbd814eb9b614 100644 --- a/src/core/server/saved_objects/import/types.ts +++ b/src/core/server/saved_objects/import/types.ts @@ -142,6 +142,7 @@ export interface SavedObjectsImportResponse { success: boolean; successCount: number; successResults?: SavedObjectsImportSuccess[]; + warnings: SavedObjectsImportWarning[]; errors?: SavedObjectsImportFailure[]; } @@ -176,3 +177,72 @@ export interface SavedObjectsResolveImportErrorsOptions { } export type CreatedObject = SavedObject & { destinationId?: string }; + +/** + * A simple informative warning that will be displayed to the user. + * + * @public + */ +export interface SavedObjectsImportSimpleWarning { + type: 'simple'; + /** The translated message to display to the user */ + message: string; +} + +/** + * A warning meant to notify that a specific user action is required to finalize the import + * of some type of object. + * + * @remark The `actionUrl` must be a path relative to the basePath, and not include it. + * + * @public + */ +export interface SavedObjectsImportActionRequiredWarning { + type: 'action_required'; + /** The translated message to display to the user. */ + message: string; + /** The path (without the basePath) that the user should be redirect to to address this warning. */ + actionPath: string; + /** An optional label to use for the link button. If unspecified, a default label will be used. */ + buttonLabel?: string; +} + +/** + * Composite type of all the possible types of import warnings. + * + * See {@link SavedObjectsImportSimpleWarning} and {@link SavedObjectsImportActionRequiredWarning} + * for more details. + * + * @public + */ +export type SavedObjectsImportWarning = + | SavedObjectsImportSimpleWarning + | SavedObjectsImportActionRequiredWarning; + +/** + * Result from a {@link SavedObjectsImportHook | import hook} + * + * @public + */ +export interface SavedObjectsImportHookResult { + /** + * An optional list of warnings to display in the UI when the import succeeds. + */ + warnings?: SavedObjectsImportWarning[]; +} + +/** + * A hook associated with a specific saved object type, that will be invoked during + * the import process. The hook will have access to the objects of the registered type. + * + * Currently, the only supported feature for import hooks is to return warnings to be displayed + * in the UI when the import succeeds. + * + * @remark The only interactions the hook can have with the import process is via the hook's + * response. Mutating the objects inside the hook's code will have no effect. + * + * @public + */ +export type SavedObjectsImportHook = ( + objects: Array> +) => SavedObjectsImportHookResult | Promise; diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index ed641ca2add2a..57dee5cd51f1d 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -23,6 +23,11 @@ export { SavedObjectsImportUnsupportedTypeError, SavedObjectsResolveImportErrorsOptions, SavedObjectsImportError, + SavedObjectsImportHook, + SavedObjectsImportHookResult, + SavedObjectsImportSimpleWarning, + SavedObjectsImportActionRequiredWarning, + SavedObjectsImportWarning, } from './import'; export { diff --git a/src/core/server/saved_objects/routes/integration_tests/import.test.ts b/src/core/server/saved_objects/routes/integration_tests/import.test.ts index af1e3257479a5..af4f57f30f968 100644 --- a/src/core/server/saved_objects/routes/integration_tests/import.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/import.test.ts @@ -101,7 +101,7 @@ describe(`POST ${URL}`, () => { ) .expect(200); - expect(result.body).toEqual({ success: true, successCount: 0 }); + expect(result.body).toEqual({ success: true, successCount: 0, warnings: [] }); expect(savedObjectsClient.bulkCreate).not.toHaveBeenCalled(); // no objects were created expect(coreUsageStatsClient.incrementSavedObjectsImport).toHaveBeenCalledWith({ request: expect.anything(), @@ -138,6 +138,7 @@ describe(`POST ${URL}`, () => { meta: { title: 'my-pattern-*', icon: 'index-pattern-icon' }, }, ], + warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( @@ -187,6 +188,7 @@ describe(`POST ${URL}`, () => { meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, }, ], + warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present }); @@ -235,6 +237,7 @@ describe(`POST ${URL}`, () => { error: { type: 'conflict' }, }, ], + warnings: [], }); expect(savedObjectsClient.bulkCreate).not.toHaveBeenCalled(); // successResults objects were not created because resolvable errors are present }); @@ -283,6 +286,7 @@ describe(`POST ${URL}`, () => { meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, }, ], + warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present }); @@ -336,6 +340,7 @@ describe(`POST ${URL}`, () => { meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, }, ], + warnings: [], }); expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1); expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith( @@ -406,6 +411,7 @@ describe(`POST ${URL}`, () => { meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, }, ], + warnings: [], }); expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1); expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith( @@ -470,6 +476,7 @@ describe(`POST ${URL}`, () => { meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, }, ], + warnings: [], }); expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1); expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith( @@ -534,6 +541,7 @@ describe(`POST ${URL}`, () => { destinationId: obj2.id, }, ], + warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( diff --git a/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts b/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts index 7df3b62ab610c..6bb2660af06fa 100644 --- a/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts @@ -113,7 +113,7 @@ describe(`POST ${URL}`, () => { ) .expect(200); - expect(result.body).toEqual({ success: true, successCount: 0 }); + expect(result.body).toEqual({ success: true, successCount: 0, warnings: [] }); expect(savedObjectsClient.bulkCreate).not.toHaveBeenCalled(); // no objects were created expect(coreUsageStatsClient.incrementSavedObjectsResolveImportErrors).toHaveBeenCalledWith({ request: expect.anything(), @@ -153,6 +153,7 @@ describe(`POST ${URL}`, () => { success: true, successCount: 1, successResults: [{ type, id, meta }], + warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( @@ -190,6 +191,7 @@ describe(`POST ${URL}`, () => { success: true, successCount: 1, successResults: [{ type, id, meta }], + warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( @@ -228,6 +230,7 @@ describe(`POST ${URL}`, () => { success: true, successCount: 1, successResults: [{ type, id, meta, overwrite: true }], + warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( @@ -271,6 +274,7 @@ describe(`POST ${URL}`, () => { meta: { title: 'Look at my visualization', icon: 'visualization-icon' }, }, ], + warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( @@ -319,6 +323,7 @@ describe(`POST ${URL}`, () => { meta: { title: 'Look at my visualization', icon: 'visualization-icon' }, }, ], + warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( @@ -383,6 +388,7 @@ describe(`POST ${URL}`, () => { destinationId: obj2.id, }, ], + warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 9021d7f56c418..6db4cf4f781b4 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -43,6 +43,7 @@ import { SavedObjectsImporter, ISavedObjectsImporter } from './import'; import { registerRoutes } from './routes'; import { ServiceStatus } from '../status'; import { calculateStatus$ } from './status'; + /** * Saved Objects is Kibana's data persistence mechanism allowing plugins to * use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index aa4ab623fe7a6..cbd8b415d9d31 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -9,6 +9,7 @@ import { SavedObjectsClient } from './service/saved_objects_client'; import { SavedObjectsTypeMappingDefinition } from './mappings'; import { SavedObjectMigrationMap } from './migrations'; +import { SavedObjectsImportHook } from './import/types'; export { SavedObjectsImportResponse, @@ -20,6 +21,9 @@ export { SavedObjectsImportUnknownError, SavedObjectsImportFailure, SavedObjectsImportRetry, + SavedObjectsImportActionRequiredWarning, + SavedObjectsImportSimpleWarning, + SavedObjectsImportWarning, } from './import/types'; import { SavedObject } from '../../types'; @@ -281,4 +285,46 @@ export interface SavedObjectsTypeManagementDefinition { * {@link Capabilities | uiCapabilities} to check if the user has permission to access the object. */ getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string }; + /** + * An optional {@link SavedObjectsImportHook | import hook} to use when importing given type. + * + * Import hooks are executed during the savedObjects import process and allow to interact + * with the imported objects. See the {@link SavedObjectsImportHook | hook documentation} + * for more info. + * + * @example + * Registering a hook displaying a warning about a specific type of object + * ```ts + * // src/plugins/my_plugin/server/plugin.ts + * import { myType } from './saved_objects'; + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * core.savedObjects.registerType({ + * ...myType, + * management: { + * ...myType.management, + * onImport: (objects) => { + * if(someActionIsNeeded(objects)) { + * return { + * warnings: [ + * { + * type: 'action_required', + * message: 'Objects need to be manually enabled after import', + * actionPath: '/app/my-app/require-activation', + * }, + * ] + * } + * } + * return {}; + * } + * }, + * }); + * } + * } + * ``` + * + * @remark messages returned in the warnings are user facing and must be translated. + */ + onImport?: SavedObjectsImportHook; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index a86e556136f78..3a8d7f4f0b0ff 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2473,6 +2473,15 @@ export interface SavedObjectsFindResult extends SavedObject { score: number; } +// @public +export interface SavedObjectsImportActionRequiredWarning { + actionPath: string; + buttonLabel?: string; + message: string; + // (undocumented) + type: 'action_required'; +} + // @public export interface SavedObjectsImportAmbiguousConflictError { // (undocumented) @@ -2542,6 +2551,14 @@ export interface SavedObjectsImportFailure { type: string; } +// @public +export type SavedObjectsImportHook = (objects: Array>) => SavedObjectsImportHookResult | Promise; + +// @public +export interface SavedObjectsImportHookResult { + warnings?: SavedObjectsImportWarning[]; +} + // @public export interface SavedObjectsImportMissingReferencesError { // (undocumented) @@ -2571,6 +2588,8 @@ export interface SavedObjectsImportResponse { successCount: number; // (undocumented) successResults?: SavedObjectsImportSuccess[]; + // (undocumented) + warnings: SavedObjectsImportWarning[]; } // @public @@ -2592,6 +2611,13 @@ export interface SavedObjectsImportRetry { type: string; } +// @public +export interface SavedObjectsImportSimpleWarning { + message: string; + // (undocumented) + type: 'simple'; +} + // @public export interface SavedObjectsImportSuccess { // @deprecated (undocumented) @@ -2625,6 +2651,9 @@ export interface SavedObjectsImportUnsupportedTypeError { type: 'unsupported_type'; } +// @public +export type SavedObjectsImportWarning = SavedObjectsImportSimpleWarning | SavedObjectsImportActionRequiredWarning; + // @public (undocumented) export interface SavedObjectsIncrementCounterField { fieldName: string; @@ -2790,6 +2819,7 @@ export interface SavedObjectsTypeManagementDefinition { getTitle?: (savedObject: SavedObject) => string; icon?: string; importableAndExportable?: boolean; + onImport?: SavedObjectsImportHook; } // @public diff --git a/src/plugins/saved_objects_management/public/lib/import_file.ts b/src/plugins/saved_objects_management/public/lib/import_file.ts index c5b3a9dc29950..a2f63571cbde4 100644 --- a/src/plugins/saved_objects_management/public/lib/import_file.ts +++ b/src/plugins/saved_objects_management/public/lib/import_file.ts @@ -6,15 +6,9 @@ * Public License, v 1. */ -import { HttpStart, SavedObjectsImportFailure } from 'src/core/public'; +import { HttpStart, SavedObjectsImportResponse } from 'src/core/public'; import { ImportMode } from '../management_section/objects_table/components/import_mode_control'; -interface ImportResponse { - success: boolean; - successCount: number; - errors?: SavedObjectsImportFailure[]; -} - export async function importFile( http: HttpStart, file: File, @@ -23,7 +17,7 @@ export async function importFile( const formData = new FormData(); formData.append('file', file); const query = createNewCopies ? { createNewCopies } : { overwrite }; - return await http.post('/api/saved_objects/_import', { + return await http.post('/api/saved_objects/_import', { body: formData, headers: { // Important to be undefined, it forces proper headers to be set for FormData diff --git a/src/plugins/saved_objects_management/public/lib/process_import_response.test.ts b/src/plugins/saved_objects_management/public/lib/process_import_response.test.ts index 5d467883448b5..a8f509ec4223d 100644 --- a/src/plugins/saved_objects_management/public/lib/process_import_response.test.ts +++ b/src/plugins/saved_objects_management/public/lib/process_import_response.test.ts @@ -11,14 +11,16 @@ import { SavedObjectsImportAmbiguousConflictError, SavedObjectsImportUnknownError, SavedObjectsImportMissingReferencesError, + SavedObjectsImportResponse, } from 'src/core/public'; import { processImportResponse } from './process_import_response'; describe('processImportResponse()', () => { test('works when no errors exist in the response', () => { - const response = { + const response: SavedObjectsImportResponse = { success: true, successCount: 0, + warnings: [], }; const result = processImportResponse(response); expect(result.status).toBe('success'); @@ -26,7 +28,7 @@ describe('processImportResponse()', () => { }); test('conflict errors get added to failedImports and result in idle status', () => { - const response = { + const response: SavedObjectsImportResponse = { success: false, successCount: 0, errors: [ @@ -39,6 +41,7 @@ describe('processImportResponse()', () => { meta: {}, }, ], + warnings: [], }; const result = processImportResponse(response); expect(result.failedImports).toMatchInlineSnapshot(` @@ -59,7 +62,7 @@ describe('processImportResponse()', () => { }); test('ambiguous conflict errors get added to failedImports and result in idle status', () => { - const response = { + const response: SavedObjectsImportResponse = { success: false, successCount: 0, errors: [ @@ -72,6 +75,7 @@ describe('processImportResponse()', () => { meta: {}, }, ], + warnings: [], }; const result = processImportResponse(response); expect(result.failedImports).toMatchInlineSnapshot(` @@ -92,7 +96,7 @@ describe('processImportResponse()', () => { }); test('unknown errors get added to failedImports and result in success status', () => { - const response = { + const response: SavedObjectsImportResponse = { success: false, successCount: 0, errors: [ @@ -105,6 +109,7 @@ describe('processImportResponse()', () => { meta: {}, }, ], + warnings: [], }; const result = processImportResponse(response); expect(result.failedImports).toMatchInlineSnapshot(` @@ -125,7 +130,7 @@ describe('processImportResponse()', () => { }); test('missing references get added to failedImports and result in idle status', () => { - const response = { + const response: SavedObjectsImportResponse = { success: false, successCount: 0, errors: [ @@ -144,6 +149,7 @@ describe('processImportResponse()', () => { meta: {}, }, ], + warnings: [], }; const result = processImportResponse(response); expect(result.failedImports).toMatchInlineSnapshot(` @@ -170,7 +176,7 @@ describe('processImportResponse()', () => { }); test('missing references get added to unmatchedReferences, but are not duplicated', () => { - const response = { + const response: SavedObjectsImportResponse = { success: false, successCount: 0, errors: [ @@ -188,6 +194,7 @@ describe('processImportResponse()', () => { meta: {}, }, ], + warnings: [], }; const result = processImportResponse(response); expect(result.unmatchedReferences).toEqual([ @@ -197,10 +204,11 @@ describe('processImportResponse()', () => { }); test('success results get added to successfulImports and result in success status', () => { - const response = { + const response: SavedObjectsImportResponse = { success: true, successCount: 1, successResults: [{ type: 'a', id: '1', meta: {} }], + warnings: [], }; const result = processImportResponse(response); expect(result.successfulImports).toMatchInlineSnapshot(` @@ -214,4 +222,22 @@ describe('processImportResponse()', () => { `); expect(result.status).toBe('success'); }); + + test('warnings from the response get returned', () => { + const response: SavedObjectsImportResponse = { + success: true, + successCount: 1, + successResults: [{ type: 'a', id: '1', meta: {} }], + warnings: [ + { + type: 'action_required', + message: 'foo', + actionPath: '/somewhere', + }, + ], + }; + const result = processImportResponse(response); + expect(result.status).toBe('success'); + expect(result.importWarnings).toEqual(response.warnings); + }); }); diff --git a/src/plugins/saved_objects_management/public/lib/process_import_response.ts b/src/plugins/saved_objects_management/public/lib/process_import_response.ts index eaee81b892995..f6f216602ab9e 100644 --- a/src/plugins/saved_objects_management/public/lib/process_import_response.ts +++ b/src/plugins/saved_objects_management/public/lib/process_import_response.ts @@ -15,6 +15,7 @@ import { SavedObjectsImportUnknownError, SavedObjectsImportFailure, SavedObjectsImportSuccess, + SavedObjectsImportWarning, } from 'src/core/public'; export interface FailedImport { @@ -41,6 +42,7 @@ export interface ProcessedImportResponse { importCount: number; conflictedSavedObjectsLinkedToSavedSearches: undefined; conflictedSearchDocs: undefined; + importWarnings: SavedObjectsImportWarning[]; } const isAnyConflict = ({ type }: FailedImport['error']) => @@ -87,5 +89,6 @@ export function processImportResponse( importCount: response.successCount, conflictedSavedObjectsLinkedToSavedSearches: undefined, conflictedSearchDocs: undefined, + importWarnings: response.warnings, }; } diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap index a48965cf7f41c..a68e8891b5ad1 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap @@ -226,6 +226,7 @@ exports[`Flyout conflicts should allow conflict resolution 2`] = ` "createNewCopies": false, "overwrite": true, }, + "importWarnings": undefined, "indexPatterns": Array [ Object { "id": "1", @@ -668,7 +669,18 @@ exports[`Flyout should render import step 1`] = ` exports[`Flyout summary should display summary when import is complete 1`] = ` `; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx index a93502c2605c0..0d8aa973bf5ea 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx @@ -18,7 +18,7 @@ import { import React from 'react'; import { shallowWithI18nProvider } from '@kbn/test/jest'; -import { coreMock } from '../../../../../../core/public/mocks'; +import { coreMock, httpServiceMock } from '../../../../../../core/public/mocks'; import { serviceRegistryMock } from '../../../services/service_registry.mock'; import { Flyout, FlyoutProps, FlyoutState } from './flyout'; import { ShallowWrapper } from 'enzyme'; @@ -47,6 +47,7 @@ describe('Flyout', () => { beforeEach(() => { const { http, overlays } = coreMock.createStart(); const search = dataPluginMock.createStartContract().search; + const basePath = httpServiceMock.createBasePath(); defaultProps = { close: jest.fn(), @@ -63,6 +64,7 @@ describe('Flyout', () => { allowedTypes: ['search', 'index-pattern', 'visualization'], serviceRegistry: serviceRegistryMock.create(), search, + basePath, }; }); diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx index c8250863ab418..39a4529d1c231 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx @@ -31,7 +31,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { OverlayStart, HttpStart } from 'src/core/public'; +import { OverlayStart, HttpStart, IBasePath } from 'src/core/public'; import { IndexPatternsContract, IIndexPattern, @@ -69,6 +69,7 @@ export interface FlyoutProps { indexPatterns: IndexPatternsContract; overlays: OverlayStart; http: HttpStart; + basePath: IBasePath; search: DataPublicPluginStart['search']; } @@ -81,6 +82,7 @@ export interface FlyoutState { failedImports?: ProcessedImportResponse['failedImports']; successfulImports?: ProcessedImportResponse['successfulImports']; conflictingRecord?: ConflictingRecord; + importWarnings?: ProcessedImportResponse['importWarnings']; error?: string; file?: File; importCount: number; @@ -616,6 +618,7 @@ export class Flyout extends Component { successfulImports = [], isLegacyFile, importMode, + importWarnings, } = this.state; if (status === 'loading') { @@ -632,8 +635,15 @@ export class Flyout extends Component { ); } - if (isLegacyFile === false && status === 'success') { - return ; + if (!isLegacyFile && status === 'success') { + return ( + + ); } // Import summary for failed legacy import diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.test.tsx index 7a7f7b2daa1a4..6cfd6f7fc57d0 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.test.tsx @@ -7,8 +7,9 @@ */ import React from 'react'; -import { ShallowWrapper } from 'enzyme'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { ReactWrapper } from 'enzyme'; +import { mountWithI18nProvider } from '@kbn/test/jest'; +import { httpServiceMock } from '../../../../../../core/public/mocks'; import { ImportSummary, ImportSummaryProps } from './import_summary'; import { FailedImport } from '../../../lib'; @@ -16,6 +17,20 @@ import { FailedImport } from '../../../lib'; import { findTestSubject } from '@elastic/eui/lib/test'; describe('ImportSummary', () => { + let basePath: ReturnType; + + const getProps = (parts: Partial): ImportSummaryProps => ({ + basePath, + failedImports: [], + successfulImports: [], + importWarnings: [], + ...parts, + }); + + beforeEach(() => { + basePath = httpServiceMock.createBasePath(); + }); + const errorUnsupportedType: FailedImport = { obj: { type: 'error-obj-type', id: 'error-obj-id', meta: { title: 'Error object' } }, error: { type: 'unsupported_type' }, @@ -28,19 +43,20 @@ describe('ImportSummary', () => { overwrite: true, }; - const findHeader = (wrapper: ShallowWrapper) => wrapper.find('h3'); - const findCountCreated = (wrapper: ShallowWrapper) => + const findHeader = (wrapper: ReactWrapper) => wrapper.find('h3'); + const findCountCreated = (wrapper: ReactWrapper) => wrapper.find('h4.savedObjectsManagementImportSummary__createdCount'); - const findCountOverwritten = (wrapper: ShallowWrapper) => + const findCountOverwritten = (wrapper: ReactWrapper) => wrapper.find('h4.savedObjectsManagementImportSummary__overwrittenCount'); - const findCountError = (wrapper: ShallowWrapper) => + const findCountError = (wrapper: ReactWrapper) => wrapper.find('h4.savedObjectsManagementImportSummary__errorCount'); - const findObjectRow = (wrapper: ShallowWrapper) => - wrapper.find('.savedObjectsManagementImportSummary__row'); + const findObjectRow = (wrapper: ReactWrapper) => + wrapper.find('.savedObjectsManagementImportSummary__row').hostNodes(); + const findWarnings = (wrapper: ReactWrapper) => wrapper.find('ImportWarning'); it('should render as expected with no results', async () => { - const props: ImportSummaryProps = { failedImports: [], successfulImports: [] }; - const wrapper = shallowWithI18nProvider(); + const props = getProps({ failedImports: [], successfulImports: [] }); + const wrapper = mountWithI18nProvider(); expect(findHeader(wrapper).childAt(0).props()).toEqual( expect.objectContaining({ values: { importCount: 0 } }) @@ -52,14 +68,14 @@ describe('ImportSummary', () => { }); it('should render as expected with a newly created object', async () => { - const props: ImportSummaryProps = { + const props = getProps({ failedImports: [], successfulImports: [successNew], - }; - const wrapper = shallowWithI18nProvider(); + }); + const wrapper = mountWithI18nProvider(); expect(findHeader(wrapper).childAt(0).props()).toEqual( - expect.not.objectContaining({ values: expect.anything() }) // no importCount for singular + expect.objectContaining({ values: { importCount: 1 } }) ); const countCreated = findCountCreated(wrapper); expect(countCreated).toHaveLength(1); @@ -68,18 +84,19 @@ describe('ImportSummary', () => { ); expect(findCountOverwritten(wrapper)).toHaveLength(0); expect(findCountError(wrapper)).toHaveLength(0); + expect(findObjectRow(wrapper)).toHaveLength(1); }); it('should render as expected with an overwritten object', async () => { - const props: ImportSummaryProps = { + const props = getProps({ failedImports: [], successfulImports: [successOverwritten], - }; - const wrapper = shallowWithI18nProvider(); + }); + const wrapper = mountWithI18nProvider(); expect(findHeader(wrapper).childAt(0).props()).toEqual( - expect.not.objectContaining({ values: expect.anything() }) // no importCount for singular + expect.objectContaining({ values: { importCount: 1 } }) ); expect(findCountCreated(wrapper)).toHaveLength(0); const countOverwritten = findCountOverwritten(wrapper); @@ -92,14 +109,14 @@ describe('ImportSummary', () => { }); it('should render as expected with an error object', async () => { - const props: ImportSummaryProps = { + const props = getProps({ failedImports: [errorUnsupportedType], successfulImports: [], - }; - const wrapper = shallowWithI18nProvider(); + }); + const wrapper = mountWithI18nProvider(); expect(findHeader(wrapper).childAt(0).props()).toEqual( - expect.not.objectContaining({ values: expect.anything() }) // no importCount for singular + expect.objectContaining({ values: { importCount: 1 } }) ); expect(findCountCreated(wrapper)).toHaveLength(0); expect(findCountOverwritten(wrapper)).toHaveLength(0); @@ -112,11 +129,11 @@ describe('ImportSummary', () => { }); it('should render as expected with mixed objects', async () => { - const props: ImportSummaryProps = { + const props = getProps({ failedImports: [errorUnsupportedType], successfulImports: [successNew, successOverwritten], - }; - const wrapper = shallowWithI18nProvider(); + }); + const wrapper = mountWithI18nProvider(); expect(findHeader(wrapper).childAt(0).props()).toEqual( expect.objectContaining({ values: { importCount: 3 } }) @@ -138,4 +155,24 @@ describe('ImportSummary', () => { ); expect(findObjectRow(wrapper)).toHaveLength(3); }); + + it('should render warnings when present', async () => { + const props = getProps({ + successfulImports: [successNew], + importWarnings: [ + { + type: 'simple', + message: 'foo', + }, + { + type: 'action_required', + message: 'bar', + actionPath: '/app/lost', + }, + ], + }); + const wrapper = mountWithI18nProvider(); + + expect(findWarnings(wrapper)).toHaveLength(2); + }); }); diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.tsx index f562c3ca3f922..201bcc6f89cf5 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.tsx @@ -8,11 +8,13 @@ import './import_summary.scss'; import _ from 'lodash'; -import React, { Fragment } from 'react'; +import React, { Fragment, FC, useMemo } from 'react'; import { EuiText, EuiFlexGroup, EuiFlexItem, + EuiCallOut, + EuiButton, EuiToolTip, EuiIcon, EuiIconTip, @@ -22,15 +24,20 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SavedObjectsImportSuccess } from 'kibana/public'; -import { FailedImport } from '../../..'; -import { getDefaultTitle, getSavedObjectLabel } from '../../../lib'; +import type { + SavedObjectsImportSuccess, + SavedObjectsImportWarning, + IBasePath, +} from 'kibana/public'; +import { getDefaultTitle, getSavedObjectLabel, FailedImport } from '../../../lib'; const DEFAULT_ICON = 'apps'; export interface ImportSummaryProps { failedImports: FailedImport[]; successfulImports: SavedObjectsImportSuccess[]; + importWarnings: SavedObjectsImportWarning[]; + basePath: IBasePath; } interface ImportItem { @@ -72,7 +79,7 @@ const mapImportSuccess = (obj: SavedObjectsImportSuccess): ImportItem => { return { type, id, title, icon, outcome }; }; -const getCountIndicators = (importItems: ImportItem[]) => { +const CountIndicators: FC<{ importItems: ImportItem[] }> = ({ importItems }) => { if (!importItems.length) { return null; } @@ -130,7 +137,8 @@ const getCountIndicators = (importItems: ImportItem[]) => { ); }; -const getStatusIndicator = ({ outcome, errorMessage = 'Error' }: ImportItem) => { +const StatusIndicator: FC<{ item: ImportItem }> = ({ item }) => { + const { outcome, errorMessage = 'Error' } = item; switch (outcome) { case 'created': return ( @@ -165,13 +173,85 @@ const getStatusIndicator = ({ outcome, errorMessage = 'Error' }: ImportItem) => } }; -export const ImportSummary = ({ failedImports, successfulImports }: ImportSummaryProps) => { - const importItems: ImportItem[] = _.sortBy( - [ - ...failedImports.map((x) => mapFailedImport(x)), - ...successfulImports.map((x) => mapImportSuccess(x)), - ], - ['type', 'title'] +const ImportWarnings: FC<{ warnings: SavedObjectsImportWarning[]; basePath: IBasePath }> = ({ + warnings, + basePath, +}) => { + if (!warnings.length) { + return null; + } + + return ( + <> + + {warnings.map((warning, index) => ( + + + {index < warnings.length - 1 && } + + ))} + + ); +}; + +const ImportWarning: FC<{ warning: SavedObjectsImportWarning; basePath: IBasePath }> = ({ + warning, + basePath, +}) => { + const warningContent = useMemo(() => { + if (warning.type === 'action_required') { + return ( + + + + {warning.buttonLabel || ( + + )} + + + + ); + } + return null; + }, [warning, basePath]); + + return ( + + {warningContent} + + ); +}; + +export const ImportSummary: FC = ({ + failedImports, + successfulImports, + importWarnings, + basePath, +}) => { + const importItems: ImportItem[] = useMemo( + () => + _.sortBy( + [ + ...failedImports.map((x) => mapFailedImport(x)), + ...successfulImports.map((x) => mapImportSuccess(x)), + ], + ['type', 'title'] + ), + [successfulImports, failedImports] ); return ( @@ -183,22 +263,16 @@ export const ImportSummary = ({ failedImports, successfulImports }: ImportSummar } >

- {importItems.length === 1 ? ( - - ) : ( - - )} +

- - {getCountIndicators(importItems)} + + + {importItems.map((item, index) => { const { type, title, icon } = item; @@ -223,7 +297,9 @@ export const ImportSummary = ({ failedImports, successfulImports }: ImportSummar

yOoL6TI8b>-HBJnuK`;amZb3}! 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

-
{getStatusIndicator(item)}
+
+ +
); diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index 69ae11038122d..bd70d34ae6854 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -501,6 +501,7 @@ export class SavedObjectsTable extends Component ); diff --git a/test/api_integration/apis/saved_objects/import.ts b/test/api_integration/apis/saved_objects/import.ts index 4dcaa4bd99932..573ad60482e27 100644 --- a/test/api_integration/apis/saved_objects/import.ts +++ b/test/api_integration/apis/saved_objects/import.ts @@ -74,6 +74,7 @@ export default function ({ getService }: FtrProviderContext) { createConflictError(visualization), createConflictError(dashboard), ], + warnings: [], }); }); }); @@ -93,6 +94,7 @@ export default function ({ getService }: FtrProviderContext) { { ...visualization, overwrite: true }, { ...dashboard, overwrite: true }, ], + warnings: [], }); }); }); @@ -119,6 +121,7 @@ export default function ({ getService }: FtrProviderContext) { error: { type: 'unsupported_type' }, }, ], + warnings: [], }); }); }); @@ -157,6 +160,7 @@ export default function ({ getService }: FtrProviderContext) { type: 'dashboard', }, ], + warnings: [], }); }); @@ -227,6 +231,7 @@ export default function ({ getService }: FtrProviderContext) { }, }, ], + warnings: [], }); }); }); diff --git a/test/api_integration/apis/saved_objects/resolve_import_errors.ts b/test/api_integration/apis/saved_objects/resolve_import_errors.ts index 5f3929f26aba6..3686c46b229b1 100644 --- a/test/api_integration/apis/saved_objects/resolve_import_errors.ts +++ b/test/api_integration/apis/saved_objects/resolve_import_errors.ts @@ -46,6 +46,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.body).to.eql({ success: true, successCount: 0, + warnings: [], }); }); }); @@ -84,6 +85,7 @@ export default function ({ getService }: FtrProviderContext) { { ...visualization, overwrite: true }, { ...dashboard, overwrite: true }, ], + warnings: [], }); }); }); @@ -125,6 +127,7 @@ export default function ({ getService }: FtrProviderContext) { error: { type: 'unsupported_type' }, }, ], + warnings: [], }); }); }); @@ -198,6 +201,7 @@ export default function ({ getService }: FtrProviderContext) { }, }, ], + warnings: [], }); }); }); @@ -215,7 +219,7 @@ export default function ({ getService }: FtrProviderContext) { .attach('file', join(__dirname, '../../fixtures/import.ndjson')) .expect(200) .then((resp) => { - expect(resp.body).to.eql({ success: true, successCount: 0 }); + expect(resp.body).to.eql({ success: true, successCount: 0, warnings: [] }); }); }); @@ -253,6 +257,7 @@ export default function ({ getService }: FtrProviderContext) { { ...visualization, overwrite: true }, { ...dashboard, overwrite: true }, ], + warnings: [], }); }); }); @@ -277,6 +282,7 @@ export default function ({ getService }: FtrProviderContext) { success: true, successCount: 1, successResults: [{ ...visualization, overwrite: true }], + warnings: [], }); }); }); @@ -328,6 +334,7 @@ export default function ({ getService }: FtrProviderContext) { meta: { title: 'My favorite vis', icon: 'visualizeApp' }, }, ], + warnings: [], }); }); await supertest diff --git a/test/functional/page_objects/management/saved_objects_page.ts b/test/functional/page_objects/management/saved_objects_page.ts index e29a9abadd881..1cdf76ad58ef0 100644 --- a/test/functional/page_objects/management/saved_objects_page.ts +++ b/test/functional/page_objects/management/saved_objects_page.ts @@ -283,6 +283,22 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv await testSubjects.click('confirmModalConfirmButton'); await this.waitTableIsLoaded(); } + + async getImportWarnings() { + const elements = await testSubjects.findAll('importSavedObjectsWarning'); + return Promise.all( + elements.map(async (element) => { + const message = await element + .findByClassName('euiCallOutHeader__title') + .then((titleEl) => titleEl.getVisibleText()); + const buttons = await element.findAllByClassName('euiButton'); + return { + message, + type: buttons.length ? 'action_required' : 'simple', + }; + }) + ); + } } return new SavedObjectsPage(); diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts index 9822ba3bee8da..2842a18c9445a 100644 --- a/test/plugin_functional/config.ts +++ b/test/plugin_functional/config.ts @@ -29,6 +29,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./test_suites/doc_views'), require.resolve('./test_suites/application_links'), require.resolve('./test_suites/data_plugin'), + require.resolve('./test_suites/saved_objects_management'), ], services: { ...functionalConfig.get('services'), diff --git a/test/plugin_functional/plugins/saved_object_hooks/kibana.json b/test/plugin_functional/plugins/saved_object_hooks/kibana.json new file mode 100644 index 0000000000000..1580e1862fac1 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_hooks/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "savedObjectHooks", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["saved_object_hooks"], + "server": true, + "ui": false +} diff --git a/test/plugin_functional/plugins/saved_object_hooks/package.json b/test/plugin_functional/plugins/saved_object_hooks/package.json new file mode 100644 index 0000000000000..9e09e5fc94be4 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_hooks/package.json @@ -0,0 +1,14 @@ +{ + "name": "saved_object_hooks", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/saved_object_hooks", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && ../../../../node_modules/.bin/tsc" + } +} \ No newline at end of file diff --git a/test/plugin_functional/plugins/saved_object_hooks/server/index.ts b/test/plugin_functional/plugins/saved_object_hooks/server/index.ts new file mode 100644 index 0000000000000..28aaa75961ddc --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_hooks/server/index.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { SavedObjectHooksPlugin } from './plugin'; + +export const plugin = () => new SavedObjectHooksPlugin(); diff --git a/test/plugin_functional/plugins/saved_object_hooks/server/plugin.ts b/test/plugin_functional/plugins/saved_object_hooks/server/plugin.ts new file mode 100644 index 0000000000000..823d9a90f29e2 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_hooks/server/plugin.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { Plugin, CoreSetup } from 'kibana/server'; + +export class SavedObjectHooksPlugin implements Plugin { + public setup({ savedObjects }: CoreSetup, deps: {}) { + savedObjects.registerType({ + name: 'test_import_warning_1', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + onImport: (objects) => { + return { + warnings: [{ type: 'simple', message: 'warning for test_import_warning_1' }], + }; + }, + }, + }); + + savedObjects.registerType({ + name: 'test_import_warning_2', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + onImport: (objects) => { + return { + warnings: [ + { + type: 'action_required', + message: 'warning for test_import_warning_2', + actionPath: '/some/url', + }, + ], + }; + }, + }, + }); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/saved_object_hooks/tsconfig.json b/test/plugin_functional/plugins/saved_object_hooks/tsconfig.json new file mode 100644 index 0000000000000..3d9d8ca9451d4 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_hooks/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [], + "references": [ + { "path": "../../../../src/core/tsconfig.json" } + ] +} diff --git a/test/plugin_functional/test_suites/saved_objects_management/exports/_import_both_types.ndjson b/test/plugin_functional/test_suites/saved_objects_management/exports/_import_both_types.ndjson new file mode 100644 index 0000000000000..d72511238e38f --- /dev/null +++ b/test/plugin_functional/test_suites/saved_objects_management/exports/_import_both_types.ndjson @@ -0,0 +1,2 @@ +{"attributes":{"title": "Test Import warnings 1"},"id":"08ff1d6a-a2e7-11e7-bb30-2e3be9be6a73","migrationVersion":{"visualization":"7.0.0"},"references":[],"type":"test_import_warning_1","version":1} +{"attributes":{"title": "Test Import warnings 2"},"id":"77bb1e6a-a2e7-11e7-bb30-2e3be9be6a73","migrationVersion":{"visualization":"7.0.0"},"references":[],"type":"test_import_warning_2","version":1} diff --git a/test/plugin_functional/test_suites/saved_objects_management/exports/_import_type_1.ndjson b/test/plugin_functional/test_suites/saved_objects_management/exports/_import_type_1.ndjson new file mode 100644 index 0000000000000..f24f73880190a --- /dev/null +++ b/test/plugin_functional/test_suites/saved_objects_management/exports/_import_type_1.ndjson @@ -0,0 +1 @@ +{"attributes":{"title": "Test Import warnings 1"},"id":"08ff1d6a-a2e7-11e7-bb30-2e3be9be6a73","migrationVersion":{"visualization":"7.0.0"},"references":[],"type":"test_import_warning_1","version":1} diff --git a/test/plugin_functional/test_suites/saved_objects_management/exports/_import_type_2.ndjson b/test/plugin_functional/test_suites/saved_objects_management/exports/_import_type_2.ndjson new file mode 100644 index 0000000000000..15efd8a6ce03d --- /dev/null +++ b/test/plugin_functional/test_suites/saved_objects_management/exports/_import_type_2.ndjson @@ -0,0 +1 @@ +{"attributes":{"title": "Test Import warnings 2"},"id":"77bb1e6a-a2e7-11e7-bb30-2e3be9be6a73","migrationVersion":{"visualization":"7.0.0"},"references":[],"type":"test_import_warning_2","version":1} diff --git a/test/plugin_functional/test_suites/saved_objects_management/import_warnings.ts b/test/plugin_functional/test_suites/saved_objects_management/import_warnings.ts new file mode 100644 index 0000000000000..71663b19b35cb --- /dev/null +++ b/test/plugin_functional/test_suites/saved_objects_management/import_warnings.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import path from 'path'; +import expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; + +export default function ({ getPageObjects }: PluginFunctionalProviderContext) { + const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects']); + + describe('import warnings', () => { + beforeEach(async () => { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + }); + + it('should display simple warnings', async () => { + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', '_import_type_1.ndjson') + ); + + await PageObjects.savedObjects.checkImportSucceeded(); + const warnings = await PageObjects.savedObjects.getImportWarnings(); + + expect(warnings).to.eql([ + { + message: 'warning for test_import_warning_1', + type: 'simple', + }, + ]); + }); + + it('should display action warnings', async () => { + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', '_import_type_2.ndjson') + ); + + await PageObjects.savedObjects.checkImportSucceeded(); + const warnings = await PageObjects.savedObjects.getImportWarnings(); + + expect(warnings).to.eql([ + { + type: 'action_required', + message: 'warning for test_import_warning_2', + }, + ]); + }); + + it('should display warnings coming from multiple types', async () => { + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', '_import_both_types.ndjson') + ); + + await PageObjects.savedObjects.checkImportSucceeded(); + const warnings = await PageObjects.savedObjects.getImportWarnings(); + + expect(warnings).to.eql([ + { + message: 'warning for test_import_warning_1', + type: 'simple', + }, + { + type: 'action_required', + message: 'warning for test_import_warning_2', + }, + ]); + }); + }); +} diff --git a/test/plugin_functional/test_suites/saved_objects_management/index.ts b/test/plugin_functional/test_suites/saved_objects_management/index.ts new file mode 100644 index 0000000000000..ad89a6605bbc5 --- /dev/null +++ b/test/plugin_functional/test_suites/saved_objects_management/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { PluginFunctionalProviderContext } from '../../services'; + +export default function ({ loadTestFile }: PluginFunctionalProviderContext) { + describe('Saved Objects Management', function () { + loadTestFile(require.resolve('./import_warnings')); + }); +} diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx index 5ede5f8a38797..2c6ec23290bb4 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx @@ -177,6 +177,7 @@ describe('CopyToSpaceFlyout', () => { 'space-1': { success: true, successCount: 3, + warnings: [], }, 'space-2': { success: false, @@ -195,6 +196,7 @@ describe('CopyToSpaceFlyout', () => { meta: {}, }, ], + warnings: [], }, }); @@ -259,10 +261,12 @@ describe('CopyToSpaceFlyout', () => { 'space-1': { success: true, successCount: 3, + warnings: [], }, 'space-2': { success: true, successCount: 3, + warnings: [], }, }); @@ -319,6 +323,7 @@ describe('CopyToSpaceFlyout', () => { 'space-1': { success: true, successCount: 5, + warnings: [], }, 'space-2': { success: false, @@ -359,6 +364,7 @@ describe('CopyToSpaceFlyout', () => { meta: {}, }, ], + warnings: [], }, }); @@ -366,6 +372,7 @@ describe('CopyToSpaceFlyout', () => { 'space-2': { success: true, successCount: 2, + warnings: [], }, }); @@ -490,6 +497,7 @@ describe('CopyToSpaceFlyout', () => { }, ], successResults: [{ type: savedObjectToCopy.type, id: savedObjectToCopy.id, meta: {} }], + warnings: [], }, }); @@ -571,6 +579,7 @@ describe('CopyToSpaceFlyout', () => { 'space-1': { success: true, successCount: 3, + warnings: [], }, 'space-2': { success: false, @@ -583,6 +592,7 @@ describe('CopyToSpaceFlyout', () => { meta: {}, }, ], + warnings: [], }, }); diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts index db731713811b4..8450fdf6b4641 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts @@ -109,6 +109,7 @@ describe('copySavedObjectsToSpaces', () => { success: true, successCount: filteredObjects.length, successResults: [('Some success(es) occurred!' as unknown) as SavedObjectsImportSuccess], + warnings: [], }; return Promise.resolve(response); @@ -201,6 +202,7 @@ describe('copySavedObjectsToSpaces', () => { success: true, successCount: filteredObjects.length, successResults: [('Some success(es) occurred!' as unknown) as SavedObjectsImportSuccess], + warnings: [], }); }, }); diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts index a558044d413d1..0f5de232177fd 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts @@ -109,6 +109,7 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => { success: true, successCount: filteredObjects.length, successResults: [('Some success(es) occurred!' as unknown) as SavedObjectsImportSuccess], + warnings: [], }; return response; @@ -209,6 +210,7 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => { success: true, successCount: filteredObjects.length, successResults: [('Some success(es) occurred!' as unknown) as SavedObjectsImportSuccess], + warnings: [], }); }, }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 65298463c9808..07befe8a26b2f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3184,8 +3184,6 @@ "savedObjectsManagement.importSummary.createdOutcomeLabel": "作成済み", "savedObjectsManagement.importSummary.errorCountHeader": "{errorCount}件のエラー", "savedObjectsManagement.importSummary.errorOutcomeLabel": "{errorMessage}", - "savedObjectsManagement.importSummary.headerLabelPlural": "{importCount}個のオブジェクトがインポートされました", - "savedObjectsManagement.importSummary.headerLabelSingular": "1個のオブジェクトがインポートされました", "savedObjectsManagement.importSummary.overwrittenCountHeader": "{overwrittenCount}件上書きされました", "savedObjectsManagement.importSummary.overwrittenOutcomeLabel": "上書き", "savedObjectsManagement.indexPattern.confirmOverwriteButton": "上書き", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7befbcf34e4d8..87af04f7dec87 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3188,8 +3188,6 @@ "savedObjectsManagement.importSummary.createdOutcomeLabel": "已创建", "savedObjectsManagement.importSummary.errorCountHeader": "{errorCount} 个错误", "savedObjectsManagement.importSummary.errorOutcomeLabel": "{errorMessage}", - "savedObjectsManagement.importSummary.headerLabelPlural": "{importCount} 个对象已导入", - "savedObjectsManagement.importSummary.headerLabelSingular": "1 个对象已导入", "savedObjectsManagement.importSummary.overwrittenCountHeader": "{overwrittenCount} 个已覆盖", "savedObjectsManagement.importSummary.overwrittenOutcomeLabel": "已覆盖", "savedObjectsManagement.indexPattern.confirmOverwriteButton": "覆盖", From f0f192c654010e2038a156add1fd777ec5f8a370 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 20 Jan 2021 11:25:48 -0700 Subject: [PATCH 22/83] [Maps] fix Maps should display better error message instead of EsError when there is no data for tracks data source (#88847) --- .../es_geo_line_source/es_geo_line_source.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx index 3693f55586b34..9c851dcedb3fa 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx @@ -9,7 +9,12 @@ import React from 'react'; import { GeoJsonProperties } from 'geojson'; import { i18n } from '@kbn/i18n'; -import { FIELD_ORIGIN, SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; +import { + EMPTY_FEATURE_COLLECTION, + FIELD_ORIGIN, + SOURCE_TYPES, + VECTOR_SHAPE_TYPE, +} from '../../../../common/constants'; import { getField, addFieldToDSL } from '../../../../common/elasticsearch_util'; import { ESGeoLineSourceDescriptor, @@ -216,6 +221,18 @@ export class ESGeoLineSource extends AbstractESAggSource { ); const totalEntities = _.get(entityResp, 'aggregations.totalEntities.value', 0); const areEntitiesTrimmed = entityBuckets.length >= MAX_TRACKS; + if (totalEntities === 0) { + return { + data: EMPTY_FEATURE_COLLECTION, + meta: { + areResultsTrimmed: false, + areEntitiesTrimmed: false, + entityCount: 0, + numTrimmedTracks: 0, + totalEntities: 0, + } as ESGeoLineSourceResponseMeta, + }; + } // // Fetch tracks From c9002a25c50b4074aac4a40f67882bf0d88aaba5 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Wed, 20 Jan 2021 13:43:53 -0500 Subject: [PATCH 23/83] [Monitoring] Convert Elasticsearch-related server files that read from _source to typescript (#88212) * A good chunk of server-side ES changes * CCR files * More areas where we just pass down the source to the client * Some more * Fix tests * Fix tests and types --- x-pack/plugins/monitoring/common/types/es.ts | 206 +++++++++++++++++- .../server/lib/apm/_get_time_of_last_event.ts | 5 +- .../monitoring/server/lib/apm/get_apm_info.ts | 3 +- .../monitoring/server/lib/apm/get_apms.ts | 3 +- .../server/lib/beats/get_beat_summary.ts | 3 +- .../monitoring/server/lib/beats/get_beats.ts | 3 +- ...clusters.js => flag_supported_clusters.ts} | 39 ++-- ...ster_license.js => get_cluster_license.ts} | 12 +- ...luster_status.js => get_cluster_status.ts} | 30 +-- ...lusters_state.js => get_clusters_state.ts} | 20 +- ...lusters_stats.js => get_clusters_stats.ts} | 21 +- .../lib/elasticsearch/{ccr.js => ccr.ts} | 15 +- ..._last_recovery.js => get_last_recovery.ts} | 17 +- .../{get_ml_jobs.js => get_ml_jobs.ts} | 30 ++- ..._index_summary.js => get_index_summary.ts} | 36 +-- .../{get_indices.js => get_indices.ts} | 69 ++++-- ...et_node_summary.js => get_node_summary.ts} | 78 ++++--- .../get_nodes/{get_nodes.js => get_nodes.ts} | 18 +- ...{handle_response.js => handle_response.ts} | 43 ++-- .../nodes/get_nodes/map_nodes_info.js | 46 ---- .../nodes/get_nodes/map_nodes_info.ts | 57 +++++ ..._allocation.js => get_shard_allocation.ts} | 24 +- .../server/lib/kibana/get_kibana_info.ts | 5 +- .../server/lib/logstash/get_node_info.ts | 3 +- .../logstash/get_pipeline_state_document.ts | 3 +- .../api/v1/elasticsearch/{ccr.js => ccr.ts} | 109 ++++++--- .../{ccr_shard.js => ccr_shard.ts} | 29 +-- x-pack/plugins/monitoring/server/types.ts | 24 -- 28 files changed, 655 insertions(+), 296 deletions(-) rename x-pack/plugins/monitoring/server/lib/cluster/{flag_supported_clusters.js => flag_supported_clusters.ts} (79%) rename x-pack/plugins/monitoring/server/lib/cluster/{get_cluster_license.js => get_cluster_license.ts} (70%) rename x-pack/plugins/monitoring/server/lib/cluster/{get_cluster_status.js => get_cluster_status.ts} (53%) rename x-pack/plugins/monitoring/server/lib/cluster/{get_clusters_state.js => get_clusters_state.ts} (82%) rename x-pack/plugins/monitoring/server/lib/cluster/{get_clusters_stats.js => get_clusters_stats.ts} (83%) rename x-pack/plugins/monitoring/server/lib/elasticsearch/{ccr.js => ccr.ts} (72%) rename x-pack/plugins/monitoring/server/lib/elasticsearch/{get_last_recovery.js => get_last_recovery.ts} (81%) rename x-pack/plugins/monitoring/server/lib/elasticsearch/{get_ml_jobs.js => get_ml_jobs.ts} (79%) rename x-pack/plugins/monitoring/server/lib/elasticsearch/indices/{get_index_summary.js => get_index_summary.ts} (73%) rename x-pack/plugins/monitoring/server/lib/elasticsearch/indices/{get_indices.js => get_indices.ts} (72%) rename x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/{get_node_summary.js => get_node_summary.ts} (59%) rename x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/{get_nodes.js => get_nodes.ts} (87%) rename x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/{handle_response.js => handle_response.ts} (57%) delete mode 100644 x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js create mode 100644 x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.ts rename x-pack/plugins/monitoring/server/lib/elasticsearch/shards/{get_shard_allocation.js => get_shard_allocation.ts} (75%) rename x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/{ccr.js => ccr.ts} (72%) rename x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/{ccr_shard.js => ccr_shard.ts} (82%) diff --git a/x-pack/plugins/monitoring/common/types/es.ts b/x-pack/plugins/monitoring/common/types/es.ts index 725ff214ae795..728cd3d73a34c 100644 --- a/x-pack/plugins/monitoring/common/types/es.ts +++ b/x-pack/plugins/monitoring/common/types/es.ts @@ -4,6 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ +export interface ElasticsearchResponse { + hits?: { + hits: ElasticsearchResponseHit[]; + total: { + value: number; + }; + }; + aggregations?: any; +} + +export interface ElasticsearchResponseHit { + _index: string; + _source: ElasticsearchSource; + inner_hits?: { + [field: string]: { + hits?: { + hits: ElasticsearchResponseHit[]; + total: { + value: number; + }; + }; + }; + }; +} + export interface ElasticsearchSourceKibanaStats { timestamp?: string; kibana?: { @@ -34,9 +59,94 @@ export interface ElasticsearchSourceLogstashPipelineVertex { }; } -export interface ElasticsearchSource { +export interface ElasticsearchNodeStats { + indices?: { + docs?: { + count?: number; + }; + store?: { + size_in_bytes?: number; + size?: { + bytes?: number; + }; + }; + }; + fs?: { + total?: { + available_in_bytes?: number; + total_in_bytes?: number; + }; + summary?: { + available?: { + bytes?: number; + }; + total?: { + bytes?: number; + }; + }; + }; + jvm?: { + mem?: { + heap_used_percent?: number; + heap?: { + used?: { + pct?: number; + }; + }; + }; + }; +} + +export interface ElasticsearchLegacySource { timestamp: string; + cluster_uuid: string; + cluster_stats?: { + nodes?: { + count?: { + total?: number; + }; + jvm?: { + max_uptime_in_millis?: number; + mem?: { + heap_used_in_bytes?: number; + heap_max_in_bytes?: number; + }; + }; + versions?: string[]; + }; + indices?: { + count?: number; + docs?: { + count?: number; + }; + shards?: { + total?: number; + }; + store?: { + size_in_bytes?: number; + }; + }; + }; + cluster_state?: { + status?: string; + nodes?: { + [nodeUuid: string]: {}; + }; + master_node?: boolean; + }; + source_node?: { + id?: string; + uuid?: string; + attributes?: {}; + transport_address?: string; + name?: string; + type?: string; + }; kibana_stats?: ElasticsearchSourceKibanaStats; + license?: { + status?: string; + type?: string; + }; logstash_state?: { pipeline?: { representation?: { @@ -108,4 +218,98 @@ export interface ElasticsearchSource { }; }; }; + stack_stats?: { + xpack?: { + ccr?: { + enabled?: boolean; + available?: boolean; + }; + }; + }; + job_stats?: { + job_id?: number; + state?: string; + data_counts?: { + processed_record_count?: number; + }; + model_size_stats?: { + model_bytes?: number; + }; + forecasts_stats?: { + total?: number; + }; + node?: { + id?: number; + name?: string; + }; + }; + index_stats?: { + index?: string; + primaries?: { + docs?: { + count?: number; + }; + store?: { + size_in_bytes?: number; + }; + indexing?: { + index_total?: number; + }; + }; + total?: { + store?: { + size_in_bytes?: number; + }; + search?: { + query_total?: number; + }; + }; + }; + node_stats?: ElasticsearchNodeStats; + service?: { + address?: string; + }; + shard?: { + index?: string; + shard?: string; + primary?: boolean; + relocating_node?: string; + node?: string; + }; + ccr_stats?: { + leader_index?: string; + follower_index?: string; + shard_id?: number; + read_exceptions?: Array<{ + exception?: { + type?: string; + }; + }>; + time_since_last_read_millis?: number; + }; + index_recovery?: { + shards?: ElasticsearchIndexRecoveryShard[]; + }; +} + +export interface ElasticsearchIndexRecoveryShard { + start_time_in_millis: number; + stop_time_in_millis: number; +} + +export interface ElasticsearchMetricbeatNode { + stats?: ElasticsearchNodeStats; +} + +export interface ElasticsearchMetricbeatSource { + elasticsearch?: { + node?: ElasticsearchLegacySource['source_node'] & ElasticsearchMetricbeatNode; + }; +} + +export type ElasticsearchSource = ElasticsearchLegacySource & ElasticsearchMetricbeatSource; + +export interface ElasticsearchModifiedSource extends ElasticsearchSource { + ccs?: string; + isSupported?: boolean; } diff --git a/x-pack/plugins/monitoring/server/lib/apm/_get_time_of_last_event.ts b/x-pack/plugins/monitoring/server/lib/apm/_get_time_of_last_event.ts index fc103959381bc..68f16cf23b474 100644 --- a/x-pack/plugins/monitoring/server/lib/apm/_get_time_of_last_event.ts +++ b/x-pack/plugins/monitoring/server/lib/apm/_get_time_of_last_event.ts @@ -8,7 +8,8 @@ import { createApmQuery } from './create_apm_query'; // @ts-ignore import { ApmClusterMetric } from '../metrics'; -import { LegacyRequest, ElasticsearchResponse } from '../../types'; +import { LegacyRequest } from '../../types'; +import { ElasticsearchResponse } from '../../../common/types/es'; export async function getTimeOfLastEvent({ req, @@ -58,5 +59,5 @@ export async function getTimeOfLastEvent({ }; const response = await callWithRequest(req, 'search', params); - return response.hits?.hits.length ? response.hits?.hits[0]._source.timestamp : undefined; + return response.hits?.hits.length ? response.hits?.hits[0]?._source.timestamp : undefined; } diff --git a/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts b/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts index 7d471d528595e..7bc36d559ac34 100644 --- a/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts +++ b/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts @@ -14,7 +14,8 @@ import { getDiffCalculation } from '../beats/_beats_stats'; // @ts-ignore import { ApmMetric } from '../metrics'; import { getTimeOfLastEvent } from './_get_time_of_last_event'; -import { LegacyRequest, ElasticsearchResponse } from '../../types'; +import { LegacyRequest } from '../../types'; +import { ElasticsearchResponse } from '../../../common/types/es'; export function handleResponse(response: ElasticsearchResponse, apmUuid: string) { if (!response.hits || response.hits.hits.length === 0) { diff --git a/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts b/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts index 7677677ea5e75..4dbd32c889760 100644 --- a/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts +++ b/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts @@ -14,7 +14,8 @@ import { createApmQuery } from './create_apm_query'; import { calculateRate } from '../calculate_rate'; // @ts-ignore import { getDiffCalculation } from './_apm_stats'; -import { LegacyRequest, ElasticsearchResponse, ElasticsearchResponseHit } from '../../types'; +import { LegacyRequest } from '../../types'; +import { ElasticsearchResponse, ElasticsearchResponseHit } from '../../../common/types/es'; export function handleResponse(response: ElasticsearchResponse, start: number, end: number) { const initial = { ids: new Set(), beats: [] }; diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts b/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts index 80b5efda4047a..0bfc4b85c9661 100644 --- a/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts +++ b/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts @@ -5,7 +5,8 @@ */ import { upperFirst } from 'lodash'; -import { LegacyRequest, ElasticsearchResponse } from '../../types'; +import { LegacyRequest } from '../../types'; +import { ElasticsearchResponse } from '../../../common/types/es'; // @ts-ignore import { checkParam } from '../error_missing_required'; // @ts-ignore diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_beats.ts b/x-pack/plugins/monitoring/server/lib/beats/get_beats.ts index aa5ef81a8de33..cd474f77d42c2 100644 --- a/x-pack/plugins/monitoring/server/lib/beats/get_beats.ts +++ b/x-pack/plugins/monitoring/server/lib/beats/get_beats.ts @@ -14,7 +14,8 @@ import { createBeatsQuery } from './create_beats_query'; import { calculateRate } from '../calculate_rate'; // @ts-ignore import { getDiffCalculation } from './_beats_stats'; -import { ElasticsearchResponse, LegacyRequest } from '../../types'; +import { LegacyRequest } from '../../types'; +import { ElasticsearchResponse } from '../../../common/types/es'; interface Beat { uuid: string | undefined; diff --git a/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.js b/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.ts similarity index 79% rename from x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.js rename to x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.ts index a1674b2f5eb36..248d1604ee20b 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.ts @@ -4,17 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { set } from '@elastic/safer-lodash-set'; -import { get, find } from 'lodash'; +// @ts-ignore import { checkParam } from '../error_missing_required'; import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../common/constants'; +import { ElasticsearchResponse, ElasticsearchModifiedSource } from '../../../common/types/es'; +import { LegacyRequest } from '../../types'; async function findSupportedBasicLicenseCluster( - req, - clusters, - kbnIndexPattern, - kibanaUuid, - serverLog + req: LegacyRequest, + clusters: ElasticsearchModifiedSource[], + kbnIndexPattern: string, + kibanaUuid: string, + serverLog: (message: string) => void ) { checkParam(kbnIndexPattern, 'kbnIndexPattern in cluster/findSupportedBasicLicenseCluster'); @@ -25,7 +26,7 @@ async function findSupportedBasicLicenseCluster( const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); const gte = req.payload.timeRange.min; const lte = req.payload.timeRange.max; - const kibanaDataResult = await callWithRequest(req, 'search', { + const kibanaDataResult: ElasticsearchResponse = (await callWithRequest(req, 'search', { index: kbnIndexPattern, size: 1, ignoreUnavailable: true, @@ -42,11 +43,13 @@ async function findSupportedBasicLicenseCluster( }, }, }, - }); - const supportedClusterUuid = get(kibanaDataResult, 'hits.hits[0]._source.cluster_uuid'); - const supportedCluster = find(clusters, { cluster_uuid: supportedClusterUuid }); - // only this basic cluster is supported - set(supportedCluster, 'isSupported', true); + })) as ElasticsearchResponse; + const supportedClusterUuid = kibanaDataResult.hits?.hits[0]?._source.cluster_uuid ?? undefined; + for (const cluster of clusters) { + if (cluster.cluster_uuid === supportedClusterUuid) { + cluster.isSupported = true; + } + } serverLog( `Found basic license admin cluster UUID for Monitoring UI support: ${supportedClusterUuid}.` @@ -69,12 +72,12 @@ async function findSupportedBasicLicenseCluster( * Non-Basic license clusters and any cluster in a single-cluster environment * are also flagged as supported in this method. */ -export function flagSupportedClusters(req, kbnIndexPattern) { +export function flagSupportedClusters(req: LegacyRequest, kbnIndexPattern: string) { checkParam(kbnIndexPattern, 'kbnIndexPattern in cluster/flagSupportedClusters'); const config = req.server.config(); - const serverLog = (msg) => req.getLogger('supported-clusters').debug(msg); - const flagAllSupported = (clusters) => { + const serverLog = (message: string) => req.getLogger('supported-clusters').debug(message); + const flagAllSupported = (clusters: ElasticsearchModifiedSource[]) => { clusters.forEach((cluster) => { if (cluster.license) { cluster.isSupported = true; @@ -83,7 +86,7 @@ export function flagSupportedClusters(req, kbnIndexPattern) { return clusters; }; - return async function (clusters) { + return async function (clusters: ElasticsearchModifiedSource[]) { // Standalone clusters are automatically supported in the UI so ignore those for // our calculations here let linkedClusterCount = 0; @@ -110,7 +113,7 @@ export function flagSupportedClusters(req, kbnIndexPattern) { // if all linked are basic licenses if (linkedClusterCount === basicLicenseCount) { - const kibanaUuid = config.get('server.uuid'); + const kibanaUuid = config.get('server.uuid') as string; return await findSupportedBasicLicenseCluster( req, clusters, diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.js b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.ts similarity index 70% rename from x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.js rename to x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.ts index bd84fbb66f962..9f3106f7c04a3 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.ts @@ -4,12 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; +// @ts-ignore import { checkParam } from '../error_missing_required'; +// @ts-ignore import { createQuery } from '../create_query'; +// @ts-ignore import { ElasticsearchMetric } from '../metrics'; +import { ElasticsearchResponse } from '../../../common/types/es'; +import { LegacyRequest } from '../../types'; -export function getClusterLicense(req, esIndexPattern, clusterUuid) { +export function getClusterLicense(req: LegacyRequest, esIndexPattern: string, clusterUuid: string) { checkParam(esIndexPattern, 'esIndexPattern in getClusterLicense'); const params = { @@ -28,7 +32,7 @@ export function getClusterLicense(req, esIndexPattern, clusterUuid) { }; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then((response) => { - return get(response, 'hits.hits[0]._source.license', {}); + return callWithRequest(req, 'search', params).then((response: ElasticsearchResponse) => { + return response.hits?.hits[0]?._source.license; }); } diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_status.js b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_status.ts similarity index 53% rename from x-pack/plugins/monitoring/server/lib/cluster/get_cluster_status.js rename to x-pack/plugins/monitoring/server/lib/cluster/get_cluster_status.ts index cef06bb473c3f..3184893d6c637 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_status.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_status.ts @@ -3,20 +3,20 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { get } from 'lodash'; +import { ElasticsearchSource } from '../../../common/types/es'; /* * @param cluster {Object} clusterStats from getClusterStatus * @param unassignedShards {Object} shardStats from getShardStats * @return top-level cluster summary data */ -export function getClusterStatus(cluster, shardStats) { - const clusterStats = get(cluster, 'cluster_stats', {}); - const clusterNodes = get(clusterStats, 'nodes', {}); - const clusterIndices = get(clusterStats, 'indices', {}); +export function getClusterStatus(cluster: ElasticsearchSource, shardStats: unknown) { + const clusterStats = cluster.cluster_stats ?? {}; + const clusterNodes = clusterStats.nodes ?? {}; + const clusterIndices = clusterStats.indices ?? {}; - const clusterTotalShards = get(clusterIndices, 'shards.total', 0); + const clusterTotalShards = clusterIndices.shards?.total ?? 0; let unassignedShardsTotal = 0; const unassignedShards = get(shardStats, 'indicesTotals.unassigned'); if (unassignedShards !== undefined) { @@ -26,17 +26,17 @@ export function getClusterStatus(cluster, shardStats) { const totalShards = clusterTotalShards + unassignedShardsTotal; return { - status: get(cluster, 'cluster_state.status', 'unknown'), + status: cluster.cluster_state?.status ?? 'unknown', // index-based stats - indicesCount: get(clusterIndices, 'count', 0), - documentCount: get(clusterIndices, 'docs.count', 0), - dataSize: get(clusterIndices, 'store.size_in_bytes', 0), + indicesCount: clusterIndices.count ?? 0, + documentCount: clusterIndices.docs?.count ?? 0, + dataSize: clusterIndices.store?.size_in_bytes ?? 0, // node-based stats - nodesCount: get(clusterNodes, 'count.total', 0), - upTime: get(clusterNodes, 'jvm.max_uptime_in_millis', 0), - version: get(clusterNodes, 'versions', null), - memUsed: get(clusterNodes, 'jvm.mem.heap_used_in_bytes', 0), - memMax: get(clusterNodes, 'jvm.mem.heap_max_in_bytes', 0), + nodesCount: clusterNodes.count?.total ?? 0, + upTime: clusterNodes.jvm?.max_uptime_in_millis ?? 0, + version: clusterNodes.versions ?? null, + memUsed: clusterNodes.jvm?.mem?.heap_used_in_bytes ?? 0, + memMax: clusterNodes.jvm?.mem?.heap_max_in_bytes ?? 0, unassignedShards: unassignedShardsTotal, totalShards, }; diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.ts similarity index 82% rename from x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.js rename to x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.ts index fa5526728086e..c752f218f9626 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.ts @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get, find } from 'lodash'; +import { find } from 'lodash'; +// @ts-ignore import { checkParam } from '../error_missing_required'; +import { ElasticsearchResponse, ElasticsearchModifiedSource } from '../../../common/types/es'; +import { LegacyRequest } from '../../types'; /** * Augment the {@clusters} with their cluster state's from the {@code response}. @@ -15,11 +18,14 @@ import { checkParam } from '../error_missing_required'; * @param {Array} clusters Array of clusters to be augmented * @return {Array} Always {@code clusters}. */ -export function handleResponse(response, clusters) { - const hits = get(response, 'hits.hits', []); +export function handleResponse( + response: ElasticsearchResponse, + clusters: ElasticsearchModifiedSource[] +) { + const hits = response.hits?.hits ?? []; hits.forEach((hit) => { - const currentCluster = get(hit, '_source', {}); + const currentCluster = hit._source; if (currentCluster) { const cluster = find(clusters, { cluster_uuid: currentCluster.cluster_uuid }); @@ -39,7 +45,11 @@ export function handleResponse(response, clusters) { * * If there is no cluster state available for any cluster, then it will be returned without any cluster state information. */ -export function getClustersState(req, esIndexPattern, clusters) { +export function getClustersState( + req: LegacyRequest, + esIndexPattern: string, + clusters: ElasticsearchModifiedSource[] +) { checkParam(esIndexPattern, 'esIndexPattern in cluster/getClustersHealth'); const clusterUuids = clusters diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts similarity index 83% rename from x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.js rename to x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts index 8ddd33837f56e..609c8fb2089de 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts @@ -4,12 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; +// @ts-ignore import { checkParam } from '../error_missing_required'; +// @ts-ignore import { createQuery } from '../create_query'; +// @ts-ignore import { ElasticsearchMetric } from '../metrics'; +// @ts-ignore import { parseCrossClusterPrefix } from '../ccs_utils'; import { getClustersState } from './get_clusters_state'; +import { ElasticsearchResponse, ElasticsearchModifiedSource } from '../../../common/types/es'; +import { LegacyRequest } from '../../types'; /** * This will fetch the cluster stats and cluster state as a single object per cluster. @@ -19,10 +24,10 @@ import { getClustersState } from './get_clusters_state'; * @param {String} clusterUuid (optional) If not undefined, getClusters will filter for a single cluster * @return {Promise} A promise containing an array of clusters. */ -export function getClustersStats(req, esIndexPattern, clusterUuid) { +export function getClustersStats(req: LegacyRequest, esIndexPattern: string, clusterUuid: string) { return ( fetchClusterStats(req, esIndexPattern, clusterUuid) - .then((response) => handleClusterStats(response, req.server)) + .then((response) => handleClusterStats(response)) // augment older documents (e.g., from 2.x - 5.4) with their cluster_state .then((clusters) => getClustersState(req, esIndexPattern, clusters)) ); @@ -36,7 +41,7 @@ export function getClustersStats(req, esIndexPattern, clusterUuid) { * @param {String} clusterUuid (optional) - if not undefined, getClusters filters for a single clusterUuid * @return {Promise} Object representing each cluster. */ -function fetchClusterStats(req, esIndexPattern, clusterUuid) { +function fetchClusterStats(req: LegacyRequest, esIndexPattern: string, clusterUuid: string) { checkParam(esIndexPattern, 'esIndexPattern in getClusters'); const config = req.server.config(); @@ -81,15 +86,15 @@ function fetchClusterStats(req, esIndexPattern, clusterUuid) { * @param {Object} response The response from Elasticsearch. * @return {Array} Objects representing each cluster. */ -export function handleClusterStats(response) { - const hits = get(response, 'hits.hits', []); +export function handleClusterStats(response: ElasticsearchResponse) { + const hits = response?.hits?.hits ?? []; return hits .map((hit) => { - const cluster = get(hit, '_source'); + const cluster = hit._source as ElasticsearchModifiedSource; if (cluster) { - const indexName = get(hit, '_index', ''); + const indexName = hit._index; const ccs = parseCrossClusterPrefix(indexName); // use CCS whenever we come across it so that we can avoid talking to other monitoring clusters whenever possible diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/ccr.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/ccr.ts similarity index 72% rename from x-pack/plugins/monitoring/server/lib/elasticsearch/ccr.js rename to x-pack/plugins/monitoring/server/lib/elasticsearch/ccr.ts index 0f0ba49f229b0..ec7ccd5ddb9af 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/ccr.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/ccr.ts @@ -4,19 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; import moment from 'moment'; +// @ts-ignore import { checkParam } from '../error_missing_required'; +// @ts-ignore import { ElasticsearchMetric } from '../metrics'; +// @ts-ignore import { createQuery } from '../create_query'; +import { ElasticsearchResponse } from '../../../common/types/es'; +import { LegacyRequest } from '../../types'; -export function handleResponse(response) { - const isEnabled = get(response, 'hits.hits[0]._source.stack_stats.xpack.ccr.enabled'); - const isAvailable = get(response, 'hits.hits[0]._source.stack_stats.xpack.ccr.available'); +export function handleResponse(response: ElasticsearchResponse) { + const isEnabled = response.hits?.hits[0]?._source.stack_stats?.xpack?.ccr?.enabled ?? undefined; + const isAvailable = + response.hits?.hits[0]?._source.stack_stats?.xpack?.ccr?.available ?? undefined; return isEnabled && isAvailable; } -export async function checkCcrEnabled(req, esIndexPattern) { +export async function checkCcrEnabled(req: LegacyRequest, esIndexPattern: string) { checkParam(esIndexPattern, 'esIndexPattern in getNodes'); const start = moment.utc(req.payload.timeRange.min).valueOf(); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.ts similarity index 81% rename from x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.js rename to x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.ts index 00e750b17d57b..31a58651ecd3b 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.ts @@ -5,9 +5,14 @@ */ import moment from 'moment'; import _ from 'lodash'; +// @ts-ignore import { checkParam } from '../error_missing_required'; +// @ts-ignore import { createQuery } from '../create_query'; +// @ts-ignore import { ElasticsearchMetric } from '../metrics'; +import { ElasticsearchResponse, ElasticsearchIndexRecoveryShard } from '../../../common/types/es'; +import { LegacyRequest } from '../../types'; /** * Filter out shard activity that we do not care about. @@ -20,8 +25,8 @@ import { ElasticsearchMetric } from '../metrics'; * @param {Number} startMs Start time in milliseconds of the polling window * @returns {boolean} true to keep */ -export function filterOldShardActivity(startMs) { - return (activity) => { +export function filterOldShardActivity(startMs: number) { + return (activity: ElasticsearchIndexRecoveryShard) => { // either it's still going and there is no stop time, or the stop time happened after we started looking for one return !_.isNumber(activity.stop_time_in_millis) || activity.stop_time_in_millis >= startMs; }; @@ -35,9 +40,9 @@ export function filterOldShardActivity(startMs) { * @param {Date} start The start time from the request payload (expected to be of type {@code Date}) * @returns {Object[]} An array of shards representing active shard activity from {@code _source.index_recovery.shards}. */ -export function handleLastRecoveries(resp, start) { - if (resp.hits.hits.length === 1) { - const data = _.get(resp.hits.hits[0], '_source.index_recovery.shards', []).filter( +export function handleLastRecoveries(resp: ElasticsearchResponse, start: number) { + if (resp.hits?.hits.length === 1) { + const data = (resp.hits?.hits[0]?._source.index_recovery?.shards ?? []).filter( filterOldShardActivity(moment.utc(start).valueOf()) ); data.sort((a, b) => b.start_time_in_millis - a.start_time_in_millis); @@ -47,7 +52,7 @@ export function handleLastRecoveries(resp, start) { return []; } -export function getLastRecovery(req, esIndexPattern) { +export function getLastRecovery(req: LegacyRequest, esIndexPattern: string) { checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getLastRecovery'); const start = req.payload.timeRange.min; diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.ts similarity index 79% rename from x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.js rename to x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.ts index 71f3633406c9b..29f5a38ca3a21 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.ts @@ -4,22 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import Bluebird from 'bluebird'; -import { includes, get } from 'lodash'; +import { includes } from 'lodash'; +// @ts-ignore import { checkParam } from '../error_missing_required'; +// @ts-ignore import { createQuery } from '../create_query'; +// @ts-ignore import { ElasticsearchMetric } from '../metrics'; import { ML_SUPPORTED_LICENSES } from '../../../common/constants'; +import { ElasticsearchResponse, ElasticsearchSource } from '../../../common/types/es'; +import { LegacyRequest } from '../../types'; /* * Get a listing of jobs along with some metric data to use for the listing */ -export function handleResponse(response) { - const hits = get(response, 'hits.hits', []); - return hits.map((hit) => get(hit, '_source.job_stats')); +export function handleResponse(response: ElasticsearchResponse) { + const hits = response.hits?.hits; + return hits?.map((hit) => hit._source.job_stats) ?? []; } -export function getMlJobs(req, esIndexPattern) { +export function getMlJobs(req: LegacyRequest, esIndexPattern: string) { checkParam(esIndexPattern, 'esIndexPattern in getMlJobs'); const config = req.server.config(); @@ -56,8 +60,12 @@ export function getMlJobs(req, esIndexPattern) { * cardinality isn't guaranteed to be accurate is the issue * but it will be as long as the precision threshold is >= the actual value */ -export function getMlJobsForCluster(req, esIndexPattern, cluster) { - const license = get(cluster, 'license', {}); +export function getMlJobsForCluster( + req: LegacyRequest, + esIndexPattern: string, + cluster: ElasticsearchSource +) { + const license = cluster.license ?? {}; if (license.status === 'active' && includes(ML_SUPPORTED_LICENSES, license.type)) { // ML is supported @@ -80,11 +88,11 @@ export function getMlJobsForCluster(req, esIndexPattern, cluster) { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then((response) => { - return get(response, 'aggregations.jobs_count.value', 0); + return callWithRequest(req, 'search', params).then((response: ElasticsearchResponse) => { + return response.aggregations.jobs_count.value ?? 0; }); } // ML is not supported - return Bluebird.resolve(null); + return Promise.resolve(null); } diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.ts similarity index 73% rename from x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.js rename to x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.ts index 6a0935f2b2d67..3257c5ac36084 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.ts @@ -5,22 +5,27 @@ */ import { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore import { checkParam } from '../../error_missing_required'; +// @ts-ignore import { createQuery } from '../../create_query'; +// @ts-ignore import { ElasticsearchMetric } from '../../metrics'; -import { i18n } from '@kbn/i18n'; +import { ElasticsearchResponse } from '../../../../common/types/es'; +import { LegacyRequest } from '../../../types'; -export function handleResponse(shardStats, indexUuid) { - return (response) => { - const indexStats = get(response, 'hits.hits[0]._source.index_stats'); - const primaries = get(indexStats, 'primaries'); - const total = get(indexStats, 'total'); +export function handleResponse(shardStats: any, indexUuid: string) { + return (response: ElasticsearchResponse) => { + const indexStats = response.hits?.hits[0]?._source.index_stats; + const primaries = indexStats?.primaries; + const total = indexStats?.total; const stats = { - documents: get(primaries, 'docs.count'), + documents: primaries?.docs?.count, dataSize: { - primaries: get(primaries, 'store.size_in_bytes'), - total: get(total, 'store.size_in_bytes'), + primaries: primaries?.store?.size_in_bytes, + total: total?.store?.size_in_bytes, }, }; @@ -55,10 +60,15 @@ export function handleResponse(shardStats, indexUuid) { } export function getIndexSummary( - req, - esIndexPattern, - shardStats, - { clusterUuid, indexUuid, start, end } + req: LegacyRequest, + esIndexPattern: string, + shardStats: any, + { + clusterUuid, + indexUuid, + start, + end, + }: { clusterUuid: string; indexUuid: string; start: number; end: number } ) { checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getIndexSummary'); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.ts similarity index 72% rename from x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.js rename to x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.ts index efea687ef8037..bf19fcf8978e9 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.ts @@ -5,43 +5,54 @@ */ import { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore import { checkParam } from '../../error_missing_required'; +// @ts-ignore import { ElasticsearchMetric } from '../../metrics'; +// @ts-ignore import { createQuery } from '../../create_query'; +// @ts-ignore import { calculateRate } from '../../calculate_rate'; +// @ts-ignore import { getUnassignedShards } from '../shards'; -import { i18n } from '@kbn/i18n'; - -export function handleResponse(resp, min, max, shardStats) { +import { ElasticsearchResponse } from '../../../../common/types/es'; +import { LegacyRequest } from '../../../types'; + +export function handleResponse( + resp: ElasticsearchResponse, + min: number, + max: number, + shardStats: any +) { // map the hits - const hits = get(resp, 'hits.hits', []); + const hits = resp?.hits?.hits ?? []; return hits.map((hit) => { - const stats = get(hit, '_source.index_stats'); - const earliestStats = get(hit, 'inner_hits.earliest.hits.hits[0]._source.index_stats'); + const stats = hit._source.index_stats; + const earliestStats = hit.inner_hits?.earliest?.hits?.hits[0]?._source.index_stats; const rateOptions = { - hitTimestamp: get(hit, '_source.timestamp'), - earliestHitTimestamp: get(hit, 'inner_hits.earliest.hits.hits[0]._source.timestamp'), + hitTimestamp: hit._source.timestamp, + earliestHitTimestamp: hit.inner_hits?.earliest?.hits?.hits[0]?._source.timestamp, timeWindowMin: min, timeWindowMax: max, }; - const earliestIndexingHit = get(earliestStats, 'primaries.indexing'); + const earliestIndexingHit = earliestStats?.primaries?.indexing; const { rate: indexRate } = calculateRate({ - latestTotal: get(stats, 'primaries.indexing.index_total'), - earliestTotal: get(earliestIndexingHit, 'index_total'), + latestTotal: stats?.primaries?.indexing?.index_total, + earliestTotal: earliestIndexingHit?.index_total, ...rateOptions, }); - const earliestSearchHit = get(earliestStats, 'total.search'); + const earliestSearchHit = earliestStats?.total?.search; const { rate: searchRate } = calculateRate({ - latestTotal: get(stats, 'total.search.query_total'), - earliestTotal: get(earliestSearchHit, 'query_total'), + latestTotal: stats?.total?.search?.query_total, + earliestTotal: earliestSearchHit?.query_total, ...rateOptions, }); - const shardStatsForIndex = get(shardStats, ['indices', stats.index]); - + const shardStatsForIndex = get(shardStats, ['indices', stats?.index ?? '']); let status; let statusSort; let unassignedShards; @@ -65,10 +76,10 @@ export function handleResponse(resp, min, max, shardStats) { } return { - name: stats.index, + name: stats?.index, status, - doc_count: get(stats, 'primaries.docs.count'), - data_size: get(stats, 'total.store.size_in_bytes'), + doc_count: stats?.primaries?.docs?.count, + data_size: stats?.total?.store?.size_in_bytes, index_rate: indexRate, search_rate: searchRate, unassigned_shards: unassignedShards, @@ -78,9 +89,14 @@ export function handleResponse(resp, min, max, shardStats) { } export function buildGetIndicesQuery( - esIndexPattern, - clusterUuid, - { start, end, size, showSystemIndices = false } + esIndexPattern: string, + clusterUuid: string, + { + start, + end, + size, + showSystemIndices = false, + }: { start: number; end: number; size: number; showSystemIndices: boolean } ) { const filters = []; if (!showSystemIndices) { @@ -134,7 +150,12 @@ export function buildGetIndicesQuery( }; } -export function getIndices(req, esIndexPattern, showSystemIndices = false, shardStats) { +export function getIndices( + req: LegacyRequest, + esIndexPattern: string, + showSystemIndices: boolean = false, + shardStats: any +) { checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getIndices'); const { min: start, max: end } = req.payload.timeRange; @@ -145,7 +166,7 @@ export function getIndices(req, esIndexPattern, showSystemIndices = false, shard start, end, showSystemIndices, - size: config.get('monitoring.ui.max_bucket_size'), + size: parseInt(config.get('monitoring.ui.max_bucket_size') || '', 10), }); const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.ts similarity index 59% rename from x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js rename to x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.ts index 06f5d5488a1ae..bf7471d77b616 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.ts @@ -5,35 +5,49 @@ */ import { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore import { checkParam } from '../../error_missing_required'; +// @ts-ignore import { createQuery } from '../../create_query'; +// @ts-ignore import { ElasticsearchMetric } from '../../metrics'; +// @ts-ignore import { getDefaultNodeFromId } from './get_default_node_from_id'; +// @ts-ignore import { calculateNodeType } from './calculate_node_type'; +// @ts-ignore import { getNodeTypeClassLabel } from './get_node_type_class_label'; -import { i18n } from '@kbn/i18n'; +import { + ElasticsearchSource, + ElasticsearchResponse, + ElasticsearchLegacySource, + ElasticsearchMetricbeatNode, +} from '../../../../common/types/es'; +import { LegacyRequest } from '../../../types'; -export function handleResponse(clusterState, shardStats, nodeUuid) { - return (response) => { +export function handleResponse( + clusterState: ElasticsearchSource['cluster_state'], + shardStats: any, + nodeUuid: string +) { + return (response: ElasticsearchResponse) => { let nodeSummary = {}; - const nodeStatsHits = get(response, 'hits.hits', []); - const nodes = nodeStatsHits.map((hit) => - get(hit, '_source.elasticsearch.node', hit._source.source_node) - ); // using [0] value because query results are sorted desc per timestamp + const nodeStatsHits = response.hits?.hits ?? []; + const nodes: Array< + ElasticsearchLegacySource['source_node'] | ElasticsearchMetricbeatNode + > = nodeStatsHits.map((hit) => hit._source.elasticsearch?.node || hit._source.source_node); // using [0] value because query results are sorted desc per timestamp const node = nodes[0] || getDefaultNodeFromId(nodeUuid); const sourceStats = - get(response, 'hits.hits[0]._source.elasticsearch.node.stats') || - get(response, 'hits.hits[0]._source.node_stats'); - const clusterNode = get(clusterState, ['nodes', nodeUuid]); + response.hits?.hits[0]?._source.elasticsearch?.node?.stats || + response.hits?.hits[0]?._source.node_stats; + const clusterNode = + clusterState && clusterState.nodes ? clusterState.nodes[nodeUuid] : undefined; const stats = { resolver: nodeUuid, - node_ids: nodes.map((node) => node.id || node.uuid), + node_ids: nodes.map((_node) => node.id || node.uuid), attributes: node.attributes, - transport_address: get( - response, - 'hits.hits[0]._source.service.address', - node.transport_address - ), + transport_address: response.hits?.hits[0]?._source.service?.address || node.transport_address, name: node.name, type: node.type, }; @@ -48,22 +62,19 @@ export function handleResponse(clusterState, shardStats, nodeUuid) { nodeSummary = { type: nodeType, - nodeTypeLabel: nodeTypeLabel, - nodeTypeClass: nodeTypeClass, + nodeTypeLabel, + nodeTypeClass, totalShards: _shardStats.shardCount, indexCount: _shardStats.indexCount, - documents: get(sourceStats, 'indices.docs.count'), + documents: sourceStats?.indices?.docs?.count, dataSize: - get(sourceStats, 'indices.store.size_in_bytes') || - get(sourceStats, 'indices.store.size.bytes'), + sourceStats?.indices?.store?.size_in_bytes || sourceStats?.indices?.store?.size?.bytes, freeSpace: - get(sourceStats, 'fs.total.available_in_bytes') || - get(sourceStats, 'fs.summary.available.bytes'), + sourceStats?.fs?.total?.available_in_bytes || sourceStats?.fs?.summary?.available?.bytes, totalSpace: - get(sourceStats, 'fs.total.total_in_bytes') || get(sourceStats, 'fs.summary.total.bytes'), + sourceStats?.fs?.total?.total_in_bytes || sourceStats?.fs?.summary?.total?.bytes, usedHeap: - get(sourceStats, 'jvm.mem.heap_used_percent') || - get(sourceStats, 'jvm.mem.heap.used.pct'), + sourceStats?.jvm?.mem?.heap_used_percent || sourceStats?.jvm?.mem?.heap?.used?.pct, status: i18n.translate('xpack.monitoring.es.nodes.onlineStatusLabel', { defaultMessage: 'Online', }), @@ -89,11 +100,16 @@ export function handleResponse(clusterState, shardStats, nodeUuid) { } export function getNodeSummary( - req, - esIndexPattern, - clusterState, - shardStats, - { clusterUuid, nodeUuid, start, end } + req: LegacyRequest, + esIndexPattern: string, + clusterState: ElasticsearchSource['cluster_state'], + shardStats: any, + { + clusterUuid, + nodeUuid, + start, + end, + }: { clusterUuid: string; nodeUuid: string; start: number; end: number } ) { checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getNodeSummary'); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.ts similarity index 87% rename from x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js rename to x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.ts index ac4fcea6150a0..1e412d2303cc0 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.ts @@ -5,13 +5,21 @@ */ import moment from 'moment'; +// @ts-ignore import { checkParam } from '../../../error_missing_required'; +// @ts-ignore import { createQuery } from '../../../create_query'; +// @ts-ignore import { calculateAuto } from '../../../calculate_auto'; +// @ts-ignore import { ElasticsearchMetric } from '../../../metrics'; +// @ts-ignore import { getMetricAggs } from './get_metric_aggs'; import { handleResponse } from './handle_response'; +// @ts-ignore import { LISTING_METRICS_NAMES, LISTING_METRICS_PATHS } from './nodes_listing_metrics'; +import { LegacyRequest } from '../../../../types'; +import { ElasticsearchModifiedSource } from '../../../../../common/types/es'; /* Run an aggregation on node_stats to get stat data for the selected time * range for all the active nodes. Every option is a key to a configuration @@ -30,7 +38,13 @@ import { LISTING_METRICS_NAMES, LISTING_METRICS_PATHS } from './nodes_listing_me * @param {Object} nodesShardCount: per-node information about shards * @return {Array} node info combined with metrics for each node from handle_response */ -export async function getNodes(req, esIndexPattern, pageOfNodes, clusterStats, nodesShardCount) { +export async function getNodes( + req: LegacyRequest, + esIndexPattern: string, + pageOfNodes: Array<{ uuid: string }>, + clusterStats: ElasticsearchModifiedSource, + nodesShardCount: { nodes: { [nodeId: string]: { shardCount: number } } } +) { checkParam(esIndexPattern, 'esIndexPattern in getNodes'); const start = moment.utc(req.payload.timeRange.min).valueOf(); @@ -45,7 +59,7 @@ export async function getNodes(req, esIndexPattern, pageOfNodes, clusterStats, n const min = start; const bucketSize = Math.max( - config.get('monitoring.ui.min_interval_seconds'), + parseInt(config.get('monitoring.ui.min_interval_seconds') as string, 10), calculateAuto(100, duration).asSeconds() ); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.ts similarity index 57% rename from x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js rename to x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.ts index 3f82e8ec3e646..3e248a06318da 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.ts @@ -6,8 +6,11 @@ import { get } from 'lodash'; import { mapNodesInfo } from './map_nodes_info'; +// @ts-ignore import { mapNodesMetrics } from './map_nodes_metrics'; +// @ts-ignore import { uncovertMetricNames } from '../../convert_metric_names'; +import { ElasticsearchResponse, ElasticsearchModifiedSource } from '../../../../../common/types/es'; /* * Process the response from the get_nodes query @@ -18,18 +21,18 @@ import { uncovertMetricNames } from '../../convert_metric_names'; * @return {Array} node info combined with metrics for each node */ export function handleResponse( - response, - clusterStats, - nodesShardCount, - pageOfNodes, + response: ElasticsearchResponse, + clusterStats: ElasticsearchModifiedSource | undefined, + nodesShardCount: { nodes: { [nodeId: string]: { shardCount: number } } } | undefined, + pageOfNodes: Array<{ uuid: string }>, timeOptions = {} ) { if (!get(response, 'hits.hits')) { return []; } - const nodeHits = get(response, 'hits.hits', []); - const nodesInfo = mapNodesInfo(nodeHits, clusterStats, nodesShardCount); + const nodeHits = response.hits?.hits ?? []; + const nodesInfo: { [key: string]: any } = mapNodesInfo(nodeHits, clusterStats, nodesShardCount); /* * Every node bucket is an object with a field for nodeId and fields for @@ -37,19 +40,29 @@ export function handleResponse( * with a sub-object for all the metrics buckets */ const nodeBuckets = get(response, 'aggregations.nodes.buckets', []); - const metricsForNodes = nodeBuckets.reduce((accum, { key: nodeId, by_date: byDate }) => { - return { - ...accum, - [nodeId]: uncovertMetricNames(byDate), - }; - }, {}); - const nodesMetrics = mapNodesMetrics(metricsForNodes, nodesInfo, timeOptions); // summarize the metrics of online nodes + const metricsForNodes = nodeBuckets.reduce( + ( + accum: { [nodeId: string]: any }, + { key: nodeId, by_date: byDate }: { key: string; by_date: any } + ) => { + return { + ...accum, + [nodeId]: uncovertMetricNames(byDate), + }; + }, + {} + ); + const nodesMetrics: { [key: string]: any } = mapNodesMetrics( + metricsForNodes, + nodesInfo, + timeOptions + ); // summarize the metrics of online nodes // nodesInfo is the source of truth for the nodeIds, where nodesMetrics will lack metrics for offline nodes const nodes = pageOfNodes.map((node) => ({ ...node, - ...nodesInfo[node.uuid], - ...nodesMetrics[node.uuid], + ...(nodesInfo && nodesInfo[node.uuid] ? nodesInfo[node.uuid] : {}), + ...(nodesMetrics && nodesMetrics[node.uuid] ? nodesMetrics[node.uuid] : {}), resolver: node.uuid, })); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js deleted file mode 100644 index 317c1cddf57ae..0000000000000 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js +++ /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 { get, isUndefined } from 'lodash'; -import { calculateNodeType, getNodeTypeClassLabel } from '../'; - -/** - * @param {Array} nodeHits: info about each node from the hits in the get_nodes query - * @param {Object} clusterStats: cluster stats from cluster state document - * @param {Object} nodesShardCount: per-node information about shards - * @return {Object} summarized info about each node keyed by nodeId - */ -export function mapNodesInfo(nodeHits, clusterStats, nodesShardCount) { - const clusterState = get(clusterStats, 'cluster_state', { nodes: {} }); - - return nodeHits.reduce((prev, node) => { - const sourceNode = get(node, '_source.source_node') || get(node, '_source.elasticsearch.node'); - - const calculatedNodeType = calculateNodeType(sourceNode, get(clusterState, 'master_node')); - const { nodeType, nodeTypeLabel, nodeTypeClass } = getNodeTypeClassLabel( - sourceNode, - calculatedNodeType - ); - const isOnline = !isUndefined(get(clusterState, ['nodes', sourceNode.uuid || sourceNode.id])); - - return { - ...prev, - [sourceNode.uuid || sourceNode.id]: { - name: sourceNode.name, - transport_address: sourceNode.transport_address, - type: nodeType, - isOnline, - nodeTypeLabel: nodeTypeLabel, - nodeTypeClass: nodeTypeClass, - shardCount: get( - nodesShardCount, - `nodes[${sourceNode.uuid || sourceNode.id}].shardCount`, - 0 - ), - }, - }; - }, {}); -} diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.ts new file mode 100644 index 0000000000000..a2e80c9ca1d96 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.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. + */ + +import { isUndefined } from 'lodash'; +// @ts-ignore +import { calculateNodeType } from '../calculate_node_type'; +// @ts-ignore +import { getNodeTypeClassLabel } from '../get_node_type_class_label'; +import { + ElasticsearchResponseHit, + ElasticsearchModifiedSource, +} from '../../../../../common/types/es'; + +/** + * @param {Array} nodeHits: info about each node from the hits in the get_nodes query + * @param {Object} clusterStats: cluster stats from cluster state document + * @param {Object} nodesShardCount: per-node information about shards + * @return {Object} summarized info about each node keyed by nodeId + */ +export function mapNodesInfo( + nodeHits: ElasticsearchResponseHit[], + clusterStats?: ElasticsearchModifiedSource, + nodesShardCount?: { nodes: { [nodeId: string]: { shardCount: number } } } +) { + const clusterState = clusterStats?.cluster_state ?? { nodes: {} }; + + return nodeHits.reduce((prev, node) => { + const sourceNode = node._source.source_node || node._source.elasticsearch?.node; + + const calculatedNodeType = calculateNodeType(sourceNode, clusterState.master_node); + const { nodeType, nodeTypeLabel, nodeTypeClass } = getNodeTypeClassLabel( + sourceNode, + calculatedNodeType + ); + const uuid = sourceNode?.uuid ?? sourceNode?.id ?? undefined; + if (!uuid) { + return prev; + } + const isOnline = !isUndefined(clusterState.nodes ? clusterState.nodes[uuid] : undefined); + + return { + ...prev, + [uuid]: { + name: sourceNode?.name, + transport_address: sourceNode?.transport_address, + type: nodeType, + isOnline, + nodeTypeLabel, + nodeTypeClass, + shardCount: nodesShardCount?.nodes[uuid]?.shardCount ?? 0, + }, + }; + }, {}); +} diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts similarity index 75% rename from x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.js rename to x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts index 40412c03b0ef9..ed37b56b7ad05 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts @@ -4,22 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; +// @ts-ignore import { checkParam } from '../../error_missing_required'; +// @ts-ignore import { createQuery } from '../../create_query'; +// @ts-ignore import { ElasticsearchMetric } from '../../metrics'; +import { ElasticsearchResponse, ElasticsearchLegacySource } from '../../../../common/types/es'; +import { LegacyRequest } from '../../../types'; -export function handleResponse(response) { - const hits = get(response, 'hits.hits'); +export function handleResponse(response: ElasticsearchResponse) { + const hits = response.hits?.hits; if (!hits) { return []; } // deduplicate any shards from earlier days with the same cluster state state_uuid - const uniqueShards = new Set(); + const uniqueShards = new Set(); // map into object with shard and source properties - return hits.reduce((shards, hit) => { + return hits.reduce((shards: Array, hit) => { const shard = hit._source.shard; if (shard) { @@ -37,9 +41,13 @@ export function handleResponse(response) { } export function getShardAllocation( - req, - esIndexPattern, - { shardFilter, stateUuid, showSystemIndices = false } + req: LegacyRequest, + esIndexPattern: string, + { + shardFilter, + stateUuid, + showSystemIndices = false, + }: { shardFilter: any; stateUuid: string; showSystemIndices: boolean } ) { checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getShardAllocation'); diff --git a/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts b/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts index 0e8903908a55e..4a0c598eec307 100644 --- a/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts +++ b/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts @@ -9,10 +9,11 @@ import { merge } from 'lodash'; import { checkParam } from '../error_missing_required'; // @ts-ignore import { calculateAvailability } from '../calculate_availability'; -import { LegacyRequest, ElasticsearchResponse } from '../../types'; +import { LegacyRequest } from '../../types'; +import { ElasticsearchResponse } from '../../../common/types/es'; export function handleResponse(resp: ElasticsearchResponse) { - const source = resp.hits?.hits[0]._source.kibana_stats; + const source = resp.hits?.hits[0]?._source.kibana_stats; const kibana = source?.kibana; return merge(kibana, { availability: calculateAvailability(source?.timestamp), diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.ts b/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.ts index ead8764607786..cdf2277424002 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.ts +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.ts @@ -9,7 +9,8 @@ import { merge } from 'lodash'; import { checkParam } from '../error_missing_required'; // @ts-ignore import { calculateAvailability } from '../calculate_availability'; -import { LegacyRequest, ElasticsearchResponse } from '../../types'; +import { LegacyRequest } from '../../types'; +import { ElasticsearchResponse } from '../../../common/types/es'; export function handleResponse(resp: ElasticsearchResponse) { const source = resp.hits?.hits[0]?._source?.logstash_stats; diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_state_document.ts b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_state_document.ts index 96419ceb4cc70..1556b44f73804 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_state_document.ts +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_state_document.ts @@ -8,7 +8,8 @@ import { createQuery } from '../create_query'; // @ts-ignore import { LogstashMetric } from '../metrics'; -import { LegacyRequest, ElasticsearchResponse } from '../../types'; +import { LegacyRequest } from '../../types'; +import { ElasticsearchResponse } from '../../../common/types/es'; export async function getPipelineStateDocument( req: LegacyRequest, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts similarity index 72% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts index 9f69ea1465c2d..6f41455ebca6a 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts @@ -7,11 +7,15 @@ import { schema } from '@kbn/config-schema'; import moment from 'moment'; import { get, groupBy } from 'lodash'; +// @ts-ignore import { handleError } from '../../../../lib/errors/handle_error'; +// @ts-ignore import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; +import { ElasticsearchResponse, ElasticsearchSource } from '../../../../../common/types/es'; +import { LegacyRequest } from '../../../../types'; -function getBucketScript(max, min) { +function getBucketScript(max: string, min: string) { return { bucket_script: { buckets_path: { @@ -23,7 +27,13 @@ function getBucketScript(max, min) { }; } -function buildRequest(req, config, esIndexPattern) { +function buildRequest( + req: LegacyRequest, + config: { + get: (key: string) => string | undefined; + }, + esIndexPattern: string +) { const min = moment.utc(req.payload.timeRange.min).valueOf(); const max = moment.utc(req.payload.timeRange.max).valueOf(); const maxBucketSize = config.get('monitoring.ui.max_bucket_size'); @@ -168,7 +178,12 @@ function buildRequest(req, config, esIndexPattern) { }; } -export function ccrRoute(server) { +export function ccrRoute(server: { + route: (p: any) => void; + config: () => { + get: (key: string) => string | undefined; + }; +}) { server.route({ method: 'POST', path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/ccr', @@ -186,14 +201,14 @@ export function ccrRoute(server) { }), }, }, - async handler(req) { + async handler(req: LegacyRequest) { const config = server.config(); const ccs = req.payload.ccs; const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); try { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - const response = await callWithRequest( + const response: ElasticsearchResponse = await callWithRequest( req, 'search', buildRequest(req, config, esIndexPattern) @@ -203,50 +218,72 @@ export function ccrRoute(server) { return { data: [] }; } - const fullStats = get(response, 'hits.hits').reduce((accum, hit) => { - const innerHits = get(hit, 'inner_hits.by_shard.hits.hits'); - const innerHitsSource = innerHits.map((innerHit) => get(innerHit, '_source.ccr_stats')); - const grouped = groupBy( - innerHitsSource, - (stat) => `${stat.follower_index}:${stat.shard_id}` - ); + const fullStats: { + [key: string]: Array>; + } = + response.hits?.hits.reduce((accum, hit) => { + const innerHits = hit.inner_hits?.by_shard.hits?.hits ?? []; + const innerHitsSource = innerHits.map( + (innerHit) => + innerHit._source.ccr_stats as NonNullable + ); + const grouped = groupBy( + innerHitsSource, + (stat) => `${stat.follower_index}:${stat.shard_id}` + ); - return { - ...accum, - ...grouped, - }; - }, {}); + return { + ...accum, + ...grouped, + }; + }, {}) ?? {}; - const buckets = get(response, 'aggregations.by_follower_index.buckets'); - const data = buckets.reduce((accum, bucket) => { + const buckets = response.aggregations.by_follower_index.buckets; + const data = buckets.reduce((accum: any, bucket: any) => { const leaderIndex = get(bucket, 'leader_index.buckets[0].key'); const remoteCluster = get( bucket, 'leader_index.buckets[0].remote_cluster.buckets[0].key' ); const follows = remoteCluster ? `${leaderIndex} on ${remoteCluster}` : leaderIndex; - const stat = { + const stat: { + [key: string]: any; + shards: Array<{ + error?: string; + opsSynced: number; + syncLagTime: number; + syncLagOps: number; + }>; + } = { id: bucket.key, index: bucket.key, follows, + shards: [], + error: undefined, + opsSynced: undefined, + syncLagTime: undefined, + syncLagOps: undefined, }; - stat.shards = get(bucket, 'by_shard_id.buckets').reduce((accum, shardBucket) => { - const fullStat = get(fullStats[`${bucket.key}:${shardBucket.key}`], '[0]', {}); - const shardStat = { - shardId: shardBucket.key, - error: fullStat.read_exceptions.length - ? fullStat.read_exceptions[0].exception.type - : null, - opsSynced: get(shardBucket, 'ops_synced.value'), - syncLagTime: fullStat.time_since_last_read_millis, - syncLagOps: get(shardBucket, 'lag_ops.value'), - syncLagOpsLeader: get(shardBucket, 'leader_lag_ops.value'), - syncLagOpsFollower: get(shardBucket, 'follower_lag_ops.value'), - }; - accum.push(shardStat); - return accum; - }, []); + stat.shards = get(bucket, 'by_shard_id.buckets').reduce( + (accum2: any, shardBucket: any) => { + const fullStat = fullStats[`${bucket.key}:${shardBucket.key}`][0] ?? {}; + const shardStat = { + shardId: shardBucket.key, + error: fullStat.read_exceptions?.length + ? fullStat.read_exceptions[0].exception?.type + : null, + opsSynced: get(shardBucket, 'ops_synced.value'), + syncLagTime: fullStat.time_since_last_read_millis, + syncLagOps: get(shardBucket, 'lag_ops.value'), + syncLagOpsLeader: get(shardBucket, 'leader_lag_ops.value'), + syncLagOpsFollower: get(shardBucket, 'follower_lag_ops.value'), + }; + accum2.push(shardStat); + return accum2; + }, + [] + ); stat.error = (stat.shards.find((shard) => shard.error) || {}).error; stat.opsSynced = stat.shards.reduce((sum, { opsSynced }) => sum + opsSynced, 0); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts similarity index 82% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts index 92458a31c6bd8..fd834cff29aa3 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts @@ -4,15 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; import moment from 'moment'; import { schema } from '@kbn/config-schema'; +// @ts-ignore import { handleError } from '../../../../lib/errors/handle_error'; +// @ts-ignore import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +// @ts-ignore import { getMetrics } from '../../../../lib/details/get_metrics'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; +import { ElasticsearchResponse } from '../../../../../common/types/es'; +import { LegacyRequest } from '../../../../types'; -function getFormattedLeaderIndex(leaderIndex) { +function getFormattedLeaderIndex(leaderIndex: string) { let leader = leaderIndex; if (leader.includes(':')) { const leaderSplit = leader.split(':'); @@ -21,7 +25,7 @@ function getFormattedLeaderIndex(leaderIndex) { return leader; } -async function getCcrStat(req, esIndexPattern, filters) { +async function getCcrStat(req: LegacyRequest, esIndexPattern: string, filters: unknown[]) { const min = moment.utc(req.payload.timeRange.min).valueOf(); const max = moment.utc(req.payload.timeRange.max).valueOf(); @@ -68,7 +72,7 @@ async function getCcrStat(req, esIndexPattern, filters) { return await callWithRequest(req, 'search', params); } -export function ccrShardRoute(server) { +export function ccrShardRoute(server: { route: (p: any) => void; config: () => {} }) { server.route({ method: 'POST', path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/ccr/{index}/shard/{shardId}', @@ -88,7 +92,7 @@ export function ccrShardRoute(server) { }), }, }, - async handler(req) { + async handler(req: LegacyRequest) { const config = server.config(); const index = req.params.index; const shardId = req.params.shardId; @@ -120,7 +124,7 @@ export function ccrShardRoute(server) { ]; try { - const [metrics, ccrResponse] = await Promise.all([ + const [metrics, ccrResponse]: [unknown, ElasticsearchResponse] = await Promise.all([ getMetrics( req, esIndexPattern, @@ -133,18 +137,15 @@ export function ccrShardRoute(server) { getCcrStat(req, esIndexPattern, filters), ]); - const stat = get(ccrResponse, 'hits.hits[0]._source.ccr_stats', {}); - const oldestStat = get( - ccrResponse, - 'hits.hits[0].inner_hits.oldest.hits.hits[0]._source.ccr_stats', - {} - ); + const stat = ccrResponse.hits?.hits[0]?._source.ccr_stats ?? {}; + const oldestStat = + ccrResponse.hits?.hits[0].inner_hits?.oldest.hits?.hits[0]?._source.ccr_stats ?? {}; return { metrics, stat, - formattedLeader: getFormattedLeaderIndex(stat.leader_index), - timestamp: get(ccrResponse, 'hits.hits[0]._source.timestamp'), + formattedLeader: getFormattedLeaderIndex(stat.leader_index ?? ''), + timestamp: ccrResponse.hits?.hits[0]?._source.timestamp, oldestStat, }; } catch (err) { diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index 47ac3beb8c390..dd6ec9c7930e5 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -17,7 +17,6 @@ import { LicensingPluginSetup } from '../../licensing/server'; import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server'; import { EncryptedSavedObjectsPluginSetup } from '../../encrypted_saved_objects/server'; import { CloudSetup } from '../../cloud/server'; -import { ElasticsearchSource } from '../common/types/es'; export interface MonitoringLicenseService { refresh: () => Promise; @@ -118,26 +117,3 @@ export interface LegacyServer { }; }; } - -export interface ElasticsearchResponse { - hits?: { - hits: ElasticsearchResponseHit[]; - total: { - value: number; - }; - }; -} - -export interface ElasticsearchResponseHit { - _source: ElasticsearchSource; - inner_hits?: { - [field: string]: { - hits?: { - hits: ElasticsearchResponseHit[]; - total: { - value: number; - }; - }; - }; - }; -} From 8b1a228c294bb1b8ff7c8bab2fdb3db34983cb66 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Wed, 20 Jan 2021 10:53:01 -0800 Subject: [PATCH 24/83] [Alerting] Migrate Actions, Alerts, Stack Alerts and TriggersActionsUI plugins to TS project references (#88556) * [Alerting] Migrate Actions plugin to TS project references * alerts plugin ts migration * triggers_actions_ui plugin ts migration * fixed build * fixed build --- .../examples/alerting_example/tsconfig.json | 2 +- .../server/lib/ensure_sufficient_license.ts | 3 +- x-pack/plugins/actions/tsconfig.json | 27 +++++++++++++++++ .../alerts/server/saved_objects/migrations.ts | 6 ++-- x-pack/plugins/alerts/tsconfig.json | 30 +++++++++++++++++++ x-pack/plugins/stack_alerts/tsconfig.json | 25 ++++++++++++++++ .../plugins/triggers_actions_ui/tsconfig.json | 29 ++++++++++++++++++ x-pack/test/tsconfig.json | 4 +++ x-pack/tsconfig.json | 10 ++++++- x-pack/tsconfig.refs.json | 4 +++ 10 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/actions/tsconfig.json create mode 100644 x-pack/plugins/alerts/tsconfig.json create mode 100644 x-pack/plugins/stack_alerts/tsconfig.json create mode 100644 x-pack/plugins/triggers_actions_ui/tsconfig.json diff --git a/x-pack/examples/alerting_example/tsconfig.json b/x-pack/examples/alerting_example/tsconfig.json index 95d42d40aceb3..99e0f1f0e7c9e 100644 --- a/x-pack/examples/alerting_example/tsconfig.json +++ b/x-pack/examples/alerting_example/tsconfig.json @@ -9,7 +9,7 @@ "public/**/*.tsx", "server/**/*.ts", "common/**/*.ts", - "../../../typings/**/*", + "../../typings/**/*", ], "exclude": [], "references": [ diff --git a/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts index f22e87a58ec7f..c4ed47b3398df 100644 --- a/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts +++ b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts @@ -6,9 +6,10 @@ import { ActionType } from '../types'; import { LICENSE_TYPE } from '../../../licensing/common/types'; import { ServerLogActionTypeId, IndexActionTypeId } from '../builtin_action_types'; -import { CASE_ACTION_TYPE_ID } from '../../../case/server'; import { ActionTypeConfig, ActionTypeSecrets, ActionTypeParams } from '../types'; +export const CASE_ACTION_TYPE_ID = '.case'; + const ACTIONS_SCOPED_WITHIN_STACK = new Set([ ServerLogActionTypeId, IndexActionTypeId, diff --git a/x-pack/plugins/actions/tsconfig.json b/x-pack/plugins/actions/tsconfig.json new file mode 100644 index 0000000000000..d5c1105c99ad0 --- /dev/null +++ b/x-pack/plugins/actions/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "server/**/*", + // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "server/**/*.json", + "common/*" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../spaces/tsconfig.json" }, + { "path": "../security/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" }, + { "path": "../task_manager/tsconfig.json" }, + { "path": "../event_log/tsconfig.json" }, + { "path": "../encrypted_saved_objects/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.ts index 1b9c5dac23b88..76696d11d5f03 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.ts @@ -11,11 +11,9 @@ import { } from '../../../../../src/core/server'; import { RawAlert } from '../types'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; -import { - APP_ID as SIEM_APP_ID, - SERVER_APP_ID as SIEM_SERVER_APP_ID, -} from '../../../security_solution/common/constants'; +const SIEM_APP_ID = 'securitySolution'; +const SIEM_SERVER_APP_ID = 'siem'; export const LEGACY_LAST_MODIFIED_VERSION = 'pre-7.10.0'; type AlertMigration = ( diff --git a/x-pack/plugins/alerts/tsconfig.json b/x-pack/plugins/alerts/tsconfig.json new file mode 100644 index 0000000000000..86ab00faeb5ad --- /dev/null +++ b/x-pack/plugins/alerts/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "server/**/*", + // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "server/**/*.json", + "public/**/*", + "common/*" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../actions/tsconfig.json" }, + { "path": "../spaces/tsconfig.json" }, + { "path": "../security/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" }, + { "path": "../task_manager/tsconfig.json" }, + { "path": "../event_log/tsconfig.json" }, + { "path": "../encrypted_saved_objects/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_utils/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/stack_alerts/tsconfig.json b/x-pack/plugins/stack_alerts/tsconfig.json new file mode 100644 index 0000000000000..ad047001f8d89 --- /dev/null +++ b/x-pack/plugins/stack_alerts/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "server/**/*", + "server/**/*.json", + "public/**/*", + "common/*" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../alerts/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../triggers_actions_ui/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/saved_objects/tsconfig.json" }, + { "path": "../../../src/plugins/data/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json new file mode 100644 index 0000000000000..715ed6848d9b7 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "server/**/*", + "public/**/*", + "common/**/*", + "config.ts", + "../../../typings/**/*" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../alerts/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../../../src/plugins/data/tsconfig.json" }, + { "path": "../../../src/plugins/saved_objects/tsconfig.json" }, + { "path": "../../../src/plugins/home/tsconfig.json" }, + { "path": "../../../src/plugins/charts/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, + { "path": "../../../src/plugins/management/tsconfig.json" }, + ] +} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index cfe328236cd36..699ff64af3f88 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -36,6 +36,8 @@ { "path": "../../src/plugins/ui_actions/tsconfig.json" }, { "path": "../../src/plugins/url_forwarding/tsconfig.json" }, + { "path": "../plugins/actions/tsconfig.json"}, + { "path": "../plugins/alerts/tsconfig.json"}, { "path": "../plugins/console_extensions/tsconfig.json" }, { "path": "../plugins/data_enhanced/tsconfig.json" }, { "path": "../plugins/global_search/tsconfig.json" }, @@ -46,10 +48,12 @@ { "path": "../plugins/licensing/tsconfig.json" }, { "path": "../plugins/task_manager/tsconfig.json" }, { "path": "../plugins/telemetry_collection_xpack/tsconfig.json" }, + { "path": "../plugins/triggers_actions_ui/tsconfig.json" }, { "path": "../plugins/ui_actions_enhanced/tsconfig.json" }, { "path": "../plugins/spaces/tsconfig.json" }, { "path": "../plugins/security/tsconfig.json" }, { "path": "../plugins/encrypted_saved_objects/tsconfig.json" }, + { "path": "../plugins/stack_alerts/tsconfig.json" }, { "path": "../plugins/beats_management/tsconfig.json" }, { "path": "../plugins/cloud/tsconfig.json" }, { "path": "../plugins/saved_objects_tagging/tsconfig.json" }, diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 812ead39ba412..ae12773023663 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -2,6 +2,8 @@ "extends": "../tsconfig.base.json", "include": ["mocks.ts", "typings/**/*", "plugins/**/*", "tasks/**/*"], "exclude": [ + "plugins/actions/**/*", + "plugins/alerts/**/*", "plugins/apm/e2e/cypress/**/*", "plugins/apm/ftr_e2e/**/*", "plugins/apm/scripts/**/*", @@ -21,10 +23,12 @@ "plugins/task_manager/**/*", "plugins/telemetry_collection_xpack/**/*", "plugins/translations/**/*", + "plugins/triggers_actions_ui/**/*", "plugins/ui_actions_enhanced/**/*", "plugins/vis_type_timeseries_enhanced/**/*", "plugins/spaces/**/*", "plugins/security/**/*", + "plugins/stack_alerts/**/*", "plugins/encrypted_saved_objects/**/*", "plugins/beats_management/**/*", "plugins/cloud/**/*", @@ -92,6 +96,10 @@ { "path": "./plugins/beats_management/tsconfig.json" }, { "path": "./plugins/cloud/tsconfig.json" }, { "path": "./plugins/saved_objects_tagging/tsconfig.json" }, - { "path": "./plugins/global_search_bar/tsconfig.json" } + { "path": "./plugins/global_search_bar/tsconfig.json" }, + { "path": "./plugins/actions/tsconfig.json"}, + { "path": "./plugins/alerts/tsconfig.json"}, + { "path": "./plugins/triggers_actions_ui/tsconfig.json"}, + { "path": "./plugins/stack_alerts/tsconfig.json"} ] } diff --git a/x-pack/tsconfig.refs.json b/x-pack/tsconfig.refs.json index edee8e228f769..02623b11ce314 100644 --- a/x-pack/tsconfig.refs.json +++ b/x-pack/tsconfig.refs.json @@ -1,6 +1,8 @@ { "include": [], "references": [ + { "path": "./plugins/actions/tsconfig.json"}, + { "path": "./plugins/alerts/tsconfig.json"}, { "path": "./plugins/dashboard_enhanced/tsconfig.json" }, { "path": "./plugins/licensing/tsconfig.json" }, { "path": "./plugins/console_extensions/tsconfig.json" }, @@ -18,8 +20,10 @@ { "path": "./plugins/ui_actions_enhanced/tsconfig.json" }, { "path": "./plugins/vis_type_timeseries_enhanced/tsconfig.json" }, { "path": "./plugins/translations/tsconfig.json" }, + { "path": "./plugins/triggers_actions_ui/tsconfig.json"}, { "path": "./plugins/spaces/tsconfig.json" }, { "path": "./plugins/security/tsconfig.json" }, + { "path": "./plugins/stack_alerts/tsconfig.json"}, { "path": "./plugins/encrypted_saved_objects/tsconfig.json" }, { "path": "./plugins/beats_management/tsconfig.json" }, { "path": "./plugins/cloud/tsconfig.json" }, From f4f6cb687cfe9d55b370503fca556d0bfbb59eab Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 20 Jan 2021 11:07:32 -0800 Subject: [PATCH 25/83] [App Search] Add new encodePathParams helper (fixes unencoded document IDs) (#88648) * Add encodePathParams helper to EnterpriseSearchRequestHandler This helper accomplishes two things: - Fixes 404s from the Enterprise Search server for user-generated IDs with special characters (e.g. ? char) - Allows us to simplify some of our createRequest calls - no longer will we have to grab request.params to create paths, this helper will do so for us * Update AS document route to use new helper - This was the primary view/API I was testing to fix this bug * Update remaining AS routes to use new helper - shorter, saves us a few lines + remove unnecessary payload: params that doesn't actually validate params Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../enterprise_search_request_handler.test.ts | 30 +++++++++++++++-- .../lib/enterprise_search_request_handler.ts | 33 ++++++++++++++++++- .../routes/app_search/analytics.test.ts | 12 ++----- .../server/routes/app_search/analytics.ts | 20 ++++------- .../routes/app_search/credentials.test.ts | 21 ++---------- .../server/routes/app_search/credentials.ts | 16 ++++----- .../routes/app_search/documents.test.ts | 15 ++------- .../server/routes/app_search/documents.ts | 24 +++++--------- .../server/routes/app_search/engines.test.ts | 10 +++--- .../server/routes/app_search/engines.ts | 16 ++++----- .../server/routes/app_search/settings.test.ts | 3 -- .../server/routes/app_search/settings.ts | 8 ++--- 12 files changed, 100 insertions(+), 108 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts index e55f997a6b51b..a27932e844177 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts @@ -116,16 +116,40 @@ describe('EnterpriseSearchRequestHandler', () => { ); }); - it('correctly encodes paths and query string parameters', async () => { + it('correctly encodes query string parameters', async () => { const requestHandler = enterpriseSearchRequestHandler.createRequest({ - path: '/api/some example', + path: '/api/example', }); await makeAPICall(requestHandler, { query: { 'page[current]': 1 } }); EnterpriseSearchAPI.shouldHaveBeenCalledWith( - 'http://localhost:3002/api/some%20example?page%5Bcurrent%5D=1' + 'http://localhost:3002/api/example?page%5Bcurrent%5D=1' ); }); + + describe('encodePathParams', () => { + it('correctly replaces :pathVariables with request.params', async () => { + const requestHandler = enterpriseSearchRequestHandler.createRequest({ + path: '/api/examples/:example/some/:id', + }); + await makeAPICall(requestHandler, { params: { example: 'hello', id: 'world' } }); + + EnterpriseSearchAPI.shouldHaveBeenCalledWith( + 'http://localhost:3002/api/examples/hello/some/world' + ); + }); + + it('correctly encodes path params as URI components', async () => { + const requestHandler = enterpriseSearchRequestHandler.createRequest({ + path: '/api/examples/:example', + }); + await makeAPICall(requestHandler, { params: { example: 'hello#@/$%^/&[]{}/";world' } }); + + EnterpriseSearchAPI.shouldHaveBeenCalledWith( + 'http://localhost:3002/api/examples/hello%23%40%2F%24%25%5E%2F%26%5B%5D%7B%7D%2F%22%3Bworld' + ); + }); + }); }); describe('response passing', () => { diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts index 8e4a817a82551..a626198ad9c4d 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts @@ -65,11 +65,12 @@ export class EnterpriseSearchRequestHandler { ) => { try { // Set up API URL + const encodedPath = this.encodePathParams(path, request.params as Record); const queryParams = { ...(request.query as object), ...params }; const queryString = !this.isEmptyObj(queryParams) ? `?${querystring.stringify(queryParams)}` : ''; - const url = encodeURI(this.enterpriseSearchUrl + path) + queryString; + const url = encodeURI(this.enterpriseSearchUrl) + encodedPath + queryString; // Set up API options const { method } = request.route; @@ -126,6 +127,36 @@ export class EnterpriseSearchRequestHandler { }; } + /** + * This path helper is similar to React Router's generatePath, but much simpler & + * does not use regexes. It enables us to pass a static '/foo/:bar/baz' string to + * createRequest({ path }) and have :bar be automatically replaced by the value of + * request.params.bar. + * It also (very importantly) wraps all URL request params with encodeURIComponent(), + * which is an extra layer of encoding required by the Enterprise Search server in + * order to correctly & safely parse user-generated IDs with special characters in + * their names - just encodeURI alone won't work. + */ + encodePathParams(path: string, params: Record) { + const hasParams = path.includes(':'); + if (!hasParams) { + return path; + } else { + return path + .split('/') + .map((pathPart) => { + const isParam = pathPart.startsWith(':'); + if (!isParam) { + return pathPart; + } else { + const pathParam = pathPart.replace(':', ''); + return encodeURIComponent(params[pathParam]); + } + }) + .join('/'); + } + } + /** * Attempt to grab a usable error body from Enterprise Search - this isn't * always possible because some of our internal endpoints send back blank diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/analytics.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/analytics.test.ts index 9ede6989052b2..f93b205059b2f 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/analytics.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/analytics.test.ts @@ -27,12 +27,8 @@ describe('analytics routes', () => { }); it('creates a request handler', () => { - mockRouter.callRoute({ - params: { engineName: 'some-engine' }, - }); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/engines/some-engine/analytics/queries', + path: '/as/engines/:engineName/analytics/queries', }); }); @@ -84,12 +80,8 @@ describe('analytics routes', () => { }); it('creates a request handler', () => { - mockRouter.callRoute({ - params: { engineName: 'some-engine', query: 'some-query' }, - }); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/engines/some-engine/analytics/query/some-query', + path: '/as/engines/:engineName/analytics/query/:query', }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/analytics.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/analytics.ts index f7d0786b27fd4..9807ca9ad7917 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/analytics.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/analytics.ts @@ -32,13 +32,9 @@ export function registerAnalyticsRoutes({ query: schema.object(queriesSchema), }, }, - async (context, request, response) => { - const { engineName } = request.params; - - return enterpriseSearchRequestHandler.createRequest({ - path: `/as/engines/${engineName}/analytics/queries`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/analytics/queries', + }) ); router.get( @@ -52,12 +48,8 @@ export function registerAnalyticsRoutes({ query: schema.object(querySchema), }, }, - async (context, request, response) => { - const { engineName, query } = request.params; - - return enterpriseSearchRequestHandler.createRequest({ - path: `/as/engines/${engineName}/analytics/query/${query}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/analytics/query/:query', + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts index af498e346529a..f6bcd4adda1fe 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts @@ -200,16 +200,8 @@ describe('credentials routes', () => { }); it('creates a request to enterprise search', () => { - const mockRequest = { - params: { - name: 'abc123', - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/credentials/abc123', + path: '/as/credentials/:name', }); }); @@ -311,7 +303,6 @@ describe('credentials routes', () => { mockRouter = new MockRouter({ method: 'delete', path: '/api/app_search/credentials/{name}', - payload: 'params', }); registerCredentialsRoutes({ @@ -321,16 +312,8 @@ describe('credentials routes', () => { }); it('creates a request to enterprise search', () => { - const mockRequest = { - params: { - name: 'abc123', - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/credentials/abc123', + path: '/as/credentials/:name', }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.ts index a5611af9bba79..29425eedef69f 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.ts @@ -81,11 +81,9 @@ export function registerCredentialsRoutes({ body: tokenSchema, }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/as/credentials/${request.params.name}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/as/credentials/:name', + }) ); router.delete( { @@ -96,10 +94,8 @@ export function registerCredentialsRoutes({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/as/credentials/${request.params.name}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/as/credentials/:name', + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts index 5f57db40cd7e6..c12a2e69057d4 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts @@ -27,13 +27,8 @@ describe('documents routes', () => { }); it('creates a request to enterprise search', () => { - mockRouter.callRoute({ - params: { engineName: 'some-engine' }, - body: { documents: [{ foo: 'bar' }] }, - }); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/engines/some-engine/documents/new', + path: '/as/engines/:engineName/documents/new', }); }); @@ -79,10 +74,8 @@ describe('document routes', () => { }); it('creates a request to enterprise search', () => { - mockRouter.callRoute({ params: { engineName: 'some-engine', documentId: '1' } }); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/engines/some-engine/documents/1', + path: '/as/engines/:engineName/documents/:documentId', }); }); }); @@ -104,10 +97,8 @@ describe('document routes', () => { }); it('creates a request to enterprise search', () => { - mockRouter.callRoute({ params: { engineName: 'some-engine', documentId: '1' } }); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/engines/some-engine/documents/1', + path: '/as/engines/:engineName/documents/:documentId', }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts index 60cd64b32479c..665691c3a9ea3 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts @@ -24,11 +24,9 @@ export function registerDocumentsRoutes({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/as/engines/${request.params.engineName}/documents/new`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/:engineName/documents/new`, + }) ); } @@ -46,11 +44,9 @@ export function registerDocumentRoutes({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/as/engines/${request.params.engineName}/documents/${request.params.documentId}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/:engineName/documents/:documentId`, + }) ); router.delete( { @@ -62,10 +58,8 @@ export function registerDocumentRoutes({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/as/engines/${request.params.engineName}/documents/${request.params.documentId}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/:engineName/documents/:documentId`, + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts index ed6847a029100..9755fff02f738 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts @@ -59,6 +59,7 @@ describe('engine routes', () => { describe('hasValidData', () => { it('should correctly validate that the response has data', () => { + mockRequestHandler.createRequest.mockClear(); const response = { meta: { page: { @@ -73,6 +74,7 @@ describe('engine routes', () => { }); it('should correctly validate that a response does not have data', () => { + mockRequestHandler.createRequest.mockClear(); const response = {}; mockRouter.callRoute(mockRequest); @@ -125,10 +127,8 @@ describe('engine routes', () => { }); it('creates a request to enterprise search', () => { - mockRouter.callRoute({ params: { name: 'some-engine' } }); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/engines/some-engine/details', + path: '/as/engines/:name/details', }); }); }); @@ -150,10 +150,8 @@ describe('engine routes', () => { }); it('creates a request to enterprise search', () => { - mockRouter.callRoute({ params: { name: 'some-engine' } }); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/engines/some-engine/overview_metrics', + path: '/as/engines/:name/overview_metrics', }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts index f9169d8795f4b..c0bbc40ff8d2d 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts @@ -54,11 +54,9 @@ export function registerEnginesRoutes({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/as/engines/${request.params.name}/details`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/:name/details`, + }) ); router.get( { @@ -69,10 +67,8 @@ export function registerEnginesRoutes({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/as/engines/${request.params.name}/overview_metrics`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/:name/overview_metrics`, + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts index be3b2632eb67d..613ecee90d989 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts @@ -26,8 +26,6 @@ describe('log settings routes', () => { }); it('creates a request to enterprise search', () => { - mockRouter.callRoute({}); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/as/log_settings', }); @@ -52,7 +50,6 @@ describe('log settings routes', () => { }); it('creates a request to enterprise search', () => { - mockRouter.callRoute({}); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/as/log_settings', }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/settings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/settings.ts index f9684cdbc060a..bec56c9e3de08 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/settings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/settings.ts @@ -40,10 +40,8 @@ export function registerSettingsRoutes({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/as/log_settings', - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/as/log_settings', + }) ); } From 15f05b51ff2b6a732b6b28f9af82f3e14a60665c Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Wed, 20 Jan 2021 14:57:37 -0500 Subject: [PATCH 26/83] [App Search] Wired up configurable Sort and Facets in Documents View (#88764) --- .../build_search_ui_config.test.ts | 53 +++++--- .../build_search_ui_config.ts | 13 +- .../build_sort_options.test.ts | 63 ++++++++++ .../search_experience/build_sort_options.ts | 29 +++++ .../documents/search_experience/constants.ts | 25 ++++ .../search_experience/search_experience.scss | 8 ++ .../search_experience.test.tsx | 78 +++++++++--- .../search_experience/search_experience.tsx | 69 ++++++++--- .../documents/search_experience/types.ts | 18 +++ .../search_experience/views/index.ts | 1 + .../views/multi_checkbox_facets_view.test.tsx | 99 +++++++++++++++ .../views/multi_checkbox_facets_view.tsx | 114 ++++++++++++++++++ .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 14 files changed, 523 insertions(+), 51 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/constants.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/types.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts index dd52f6b8227ba..a87b73bd4e143 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts @@ -15,24 +15,49 @@ describe('buildSearchUIConfig', () => { foo: 'text' as SchemaTypes, bar: 'number' as SchemaTypes, }; + const fields = { + filterFields: ['fieldA', 'fieldB'], + sortFields: [], + }; - const config = buildSearchUIConfig(connector, schema); - expect(config.apiConnector).toEqual(connector); - expect(config.searchQuery.result_fields).toEqual({ - bar: { - raw: {}, - snippet: { - fallback: true, - size: 300, - }, + const config = buildSearchUIConfig(connector, schema, fields); + expect(config).toEqual({ + alwaysSearchOnInitialLoad: true, + apiConnector: connector, + initialState: { + sortDirection: 'desc', + sortField: 'id', }, - foo: { - raw: {}, - snippet: { - fallback: true, - size: 300, + searchQuery: { + disjunctiveFacets: ['fieldA', 'fieldB'], + facets: { + fieldA: { + size: 30, + type: 'value', + }, + fieldB: { + size: 30, + type: 'value', + }, + }, + result_fields: { + bar: { + raw: {}, + snippet: { + fallback: true, + size: 300, + }, + }, + foo: { + raw: {}, + snippet: { + fallback: true, + size: 300, + }, + }, }, }, + trackUrlState: false, }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts index 78e1fa9e7f3a2..ac10e2db7191e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts @@ -5,8 +5,17 @@ */ import { Schema } from '../../../../shared/types'; +import { Fields } from './types'; + +export const buildSearchUIConfig = (apiConnector: object, schema: Schema, fields: Fields) => { + const facets = fields.filterFields.reduce( + (facetsConfig, fieldName) => ({ + ...facetsConfig, + [fieldName]: { type: 'value', size: 30 }, + }), + {} + ); -export const buildSearchUIConfig = (apiConnector: object, schema: Schema) => { return { alwaysSearchOnInitialLoad: true, apiConnector, @@ -16,6 +25,8 @@ export const buildSearchUIConfig = (apiConnector: object, schema: Schema) => { sortField: 'id', }, searchQuery: { + disjunctiveFacets: fields.filterFields, + facets, result_fields: Object.keys(schema).reduce((acc: { [key: string]: object }, key: string) => { acc[key] = { snippet: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.test.ts new file mode 100644 index 0000000000000..e95f91f6f6f89 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.test.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { buildSortOptions } from './build_sort_options'; + +describe('buildSortOptions', () => { + it('builds sort options from a list of field names', () => { + const sortOptions = buildSortOptions( + { + filterFields: [], + sortFields: ['fieldA', 'fieldB'], + }, + [ + { + name: 'Relevance (asc)', + value: 'id', + direction: 'desc', + }, + { + name: 'Relevance (desc)', + value: 'id', + direction: 'asc', + }, + ] + ); + + expect(sortOptions).toEqual([ + { + name: 'Relevance (asc)', + value: 'id', + direction: 'desc', + }, + { + name: 'Relevance (desc)', + value: 'id', + direction: 'asc', + }, + { + direction: 'asc', + name: 'fieldA (asc)', + value: 'fieldA', + }, + { + direction: 'desc', + name: 'fieldA (desc)', + value: 'fieldA', + }, + { + direction: 'asc', + name: 'fieldB (asc)', + value: 'fieldB', + }, + { + direction: 'desc', + name: 'fieldB (desc)', + value: 'fieldB', + }, + ]); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.ts new file mode 100644 index 0000000000000..8b80e557e4b60 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { flatten } from 'lodash'; + +import { Fields, SortOption, SortDirection } from './types'; +import { ASCENDING, DESCENDING } from './constants'; + +const fieldNameToSortOptions = (fieldName: string): SortOption[] => + ['asc', 'desc'].map((direction) => ({ + name: direction === 'asc' ? ASCENDING(fieldName) : DESCENDING(fieldName), + value: fieldName, + direction: direction as SortDirection, + })); + +/** + * Adds two sort options for a given field, a "desc" and an "asc" option. + */ +export const buildSortOptions = ( + fields: Fields, + defaultSortOptions: SortOption[] +): SortOption[] => { + const sortFieldsOptions = flatten(fields.sortFields.map(fieldNameToSortOptions)); + const sortingOptions = [...defaultSortOptions, ...sortFieldsOptions]; + return sortingOptions; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/constants.ts new file mode 100644 index 0000000000000..1c7c3f5a65bd5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/constants.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ASCENDING = (fieldName: string) => + i18n.translate( + 'xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.ascendingDropDownOptionLabel', + { + defaultMessage: '{fieldName} (asc)', + values: { fieldName }, + } + ); + +export const DESCENDING = (fieldName: string) => + i18n.translate( + 'xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.descendingDropDownOptionLabel', + { + defaultMessage: '{fieldName} (desc)', + values: { fieldName }, + } + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.scss index ba9931dc90fdc..d2e0a8155fa55 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.scss @@ -25,4 +25,12 @@ background-color: $euiPageBackgroundColor; padding: $euiSizeL; } + + .documentsSearchExperience__facet { + line-height: 0; + + .euiCheckbox__label { + @include euiTextTruncate; + } + } } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx index 5df132a27bbb3..410a4ea5bab7b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx @@ -7,25 +7,19 @@ import '../../../../__mocks__/enterprise_search_url.mock'; import { setMockValues } from '../../../../__mocks__'; -const mockSetFields = jest.fn(); - jest.mock('../../../../shared/use_local_storage', () => ({ - useLocalStorage: jest.fn(() => [ - { - filterFields: ['a', 'b', 'c'], - sortFields: ['d', 'c'], - }, - mockSetFields, - ]), + useLocalStorage: jest.fn(), })); +import { useLocalStorage } from '../../../../shared/use_local_storage'; import React from 'react'; // @ts-expect-error types are not available for this package yet -import { SearchProvider } from '@elastic/react-search-ui'; -import { shallow } from 'enzyme'; +import { SearchProvider, Facet } from '@elastic/react-search-ui'; +import { shallow, ShallowWrapper } from 'enzyme'; import { CustomizationCallout } from './customization_callout'; import { CustomizationModal } from './customization_modal'; +import { Fields } from './types'; import { SearchExperience } from './search_experience'; @@ -36,8 +30,16 @@ describe('SearchExperience', () => { apiKey: '1234', }, }; + const mockSetFields = jest.fn(); + const setFieldsInLocalStorage = (fields: Fields) => { + (useLocalStorage as jest.Mock).mockImplementation(() => [fields, mockSetFields]); + }; beforeEach(() => { + setFieldsInLocalStorage({ + filterFields: ['a', 'b', 'c'], + sortFields: ['d', 'c'], + }); jest.clearAllMocks(); setMockValues(values); }); @@ -47,12 +49,60 @@ describe('SearchExperience', () => { expect(wrapper.find(SearchProvider).length).toBe(1); }); + describe('when there are no selected filter fields', () => { + let wrapper: ShallowWrapper; + beforeEach(() => { + setFieldsInLocalStorage({ + filterFields: [], + sortFields: ['a', 'b'], + }); + wrapper = shallow(); + }); + + it('shows a customize callout instead of a button if no fields are yet selected', () => { + expect(wrapper.find(CustomizationCallout).exists()).toBe(true); + expect(wrapper.find('[data-test-subj="customize"]').exists()).toBe(false); + }); + + it('will show the customization modal when clicked', () => { + expect(wrapper.find(CustomizationModal).exists()).toBe(false); + wrapper.find(CustomizationCallout).simulate('click'); + + expect(wrapper.find(CustomizationModal).exists()).toBe(true); + }); + }); + + describe('when there are selected filter fields', () => { + let wrapper: ShallowWrapper; + beforeEach(() => { + setFieldsInLocalStorage({ + filterFields: ['a', 'b'], + sortFields: ['a', 'b'], + }); + wrapper = shallow(); + }); + + it('shows a customize button', () => { + expect(wrapper.find(CustomizationCallout).exists()).toBe(false); + expect(wrapper.find('[data-test-subj="customize"]').exists()).toBe(true); + }); + }); + + it('renders Facet components for filter fields', () => { + setFieldsInLocalStorage({ + filterFields: ['a', 'b', 'c'], + sortFields: [], + }); + const wrapper = shallow(); + expect(wrapper.find(Facet).length).toBe(3); + }); + describe('customization modal', () => { it('has a customization modal which can be opened and closed', () => { const wrapper = shallow(); expect(wrapper.find(CustomizationModal).exists()).toBe(false); - wrapper.find(CustomizationCallout).simulate('click'); + wrapper.find('[data-test-subj="customize"]').simulate('click'); expect(wrapper.find(CustomizationModal).exists()).toBe(true); wrapper.find(CustomizationModal).prop('onClose')(); @@ -61,14 +111,14 @@ describe('SearchExperience', () => { it('passes values from localStorage to the customization modal', () => { const wrapper = shallow(); - wrapper.find(CustomizationCallout).simulate('click'); + wrapper.find('[data-test-subj="customize"]').simulate('click'); expect(wrapper.find(CustomizationModal).prop('filterFields')).toEqual(['a', 'b', 'c']); expect(wrapper.find(CustomizationModal).prop('sortFields')).toEqual(['d', 'c']); }); it('updates selected fields in localStorage and closes modal on save', () => { const wrapper = shallow(); - wrapper.find(CustomizationCallout).simulate('click'); + wrapper.find('[data-test-subj="customize"]').simulate('click'); wrapper.find(CustomizationModal).prop('onSave')({ filterFields: ['new', 'filters'], sortFields: ['new', 'sorts'], diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx index e80ab2e18b2d3..d829042bef11f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx @@ -7,36 +7,41 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { useValues } from 'kea'; -import { EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiButton, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; // @ts-expect-error types are not available for this package yet; -import { SearchProvider, SearchBox, Sorting } from '@elastic/react-search-ui'; +import { SearchProvider, SearchBox, Sorting, Facet } from '@elastic/react-search-ui'; // @ts-expect-error types are not available for this package yet import AppSearchAPIConnector from '@elastic/search-ui-app-search-connector'; import './search_experience.scss'; -import { EngineLogic } from '../../engine'; import { externalUrl } from '../../../../shared/enterprise_search_url'; import { useLocalStorage } from '../../../../shared/use_local_storage'; +import { EngineLogic } from '../../engine'; -import { SearchBoxView, SortingView } from './views'; +import { Fields, SortOption } from './types'; +import { SearchBoxView, SortingView, MultiCheckboxFacetsView } from './views'; import { SearchExperienceContent } from './search_experience_content'; import { buildSearchUIConfig } from './build_search_ui_config'; import { CustomizationCallout } from './customization_callout'; import { CustomizationModal } from './customization_modal'; +import { buildSortOptions } from './build_sort_options'; +import { ASCENDING, DESCENDING } from './constants'; -const DEFAULT_SORT_OPTIONS = [ +const RECENTLY_UPLOADED = i18n.translate( + 'xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.recentlyUploaded', + { + defaultMessage: 'Recently Uploaded', + } +); +const DEFAULT_SORT_OPTIONS: SortOption[] = [ { - name: i18n.translate('xpack.enterpriseSearch.appSearch.documents.search.recentlyUploadedDesc', { - defaultMessage: 'Recently Uploaded (desc)', - }), + name: DESCENDING(RECENTLY_UPLOADED), value: 'id', direction: 'desc', }, { - name: i18n.translate('xpack.enterpriseSearch.appSearch.documents.search.recentlyUploadedAsc', { - defaultMessage: 'Recently Uploaded (asc)', - }), + name: ASCENDING(RECENTLY_UPLOADED), value: 'id', direction: 'asc', }, @@ -50,16 +55,15 @@ export const SearchExperience: React.FC = () => { const openCustomizationModal = () => setShowCustomizationModal(true); const closeCustomizationModal = () => setShowCustomizationModal(false); - const [fields, setFields] = useLocalStorage( + const [fields, setFields] = useLocalStorage( `documents-search-experience-customization--${engine.name}`, { - filterFields: [] as string[], - sortFields: [] as string[], + filterFields: [], + sortFields: [], } ); - // TODO const sortFieldsOptions = _flatten(fields.sortFields.map(fieldNameToSortOptions)) // we need to flatten this array since fieldNameToSortOptions returns an array of two sorting options - const sortingOptions = [...DEFAULT_SORT_OPTIONS /* TODO ...sortFieldsOptions*/]; + const sortingOptions = buildSortOptions(fields, DEFAULT_SORT_OPTIONS); const connector = new AppSearchAPIConnector({ cacheResponses: false, @@ -68,7 +72,7 @@ export const SearchExperience: React.FC = () => { searchKey: engine.apiKey, }); - const searchProviderConfig = buildSearchUIConfig(connector, engine.schema || {}); + const searchProviderConfig = buildSearchUIConfig(connector, engine.schema || {}, fields); return (
@@ -101,7 +105,36 @@ export const SearchExperience: React.FC = () => { view={SortingView} /> - + {fields.filterFields.length > 0 ? ( + <> + {fields.filterFields.map((fieldName) => ( +
+ + +
+ ))} + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documents.search.customizationButton', + { + defaultMessage: 'Customize filters and sort', + } + )} + + + ) : ( + + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/types.ts new file mode 100644 index 0000000000000..0cde0f94b7738 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/types.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface Fields { + filterFields: string[]; + sortFields: string[]; +} + +export type SortDirection = 'asc' | 'desc'; + +export interface SortOption { + name: string; + value: string; + direction: SortDirection; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts index 8c88fc81d3a3c..7032fa1a9a06b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts @@ -9,3 +9,4 @@ export { SortingView } from './sorting_view'; export { ResultView } from './result_view'; export { ResultsPerPageView } from './results_per_page_view'; export { PagingView } from './paging_view'; +export { MultiCheckboxFacetsView } from './multi_checkbox_facets_view'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx new file mode 100644 index 0000000000000..7f43ca12652c6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { MultiCheckboxFacetsView } from './multi_checkbox_facets_view'; + +describe('MultiCheckboxFacetsView', () => { + const props = { + label: 'foo', + options: [ + { + value: 'value1', + selected: false, + }, + { + value: 'value2', + selected: false, + }, + ], + showMore: true, + onMoreClick: jest.fn(), + onRemove: jest.fn(), + onSelect: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.isEmptyRender()).toBe(false); + }); + + it('calls onMoreClick when more button is clicked', () => { + const wrapper = shallow(); + wrapper.find('[data-test-subj="more"]').simulate('click'); + expect(props.onMoreClick).toHaveBeenCalled(); + }); + + it('calls onSelect when an option is selected', () => { + const wrapper = shallow(); + wrapper.find('[data-test-subj="checkbox-group"]').simulate('change', 'generated-id_1'); + expect(props.onSelect).toHaveBeenCalledWith('value2'); + }); + + it('calls onRemove if the option was already selected', () => { + const wrapper = shallow( + + ); + wrapper.find('[data-test-subj="checkbox-group"]').simulate('change', 'generated-id_1'); + expect(props.onRemove).toHaveBeenCalledWith('value2'); + }); + + it('it passes options to EuiCheckboxGroup, converting no values to the text "No Value"', () => { + const wrapper = shallow( + + ); + const options = wrapper.find('[data-test-subj="checkbox-group"]').prop('options'); + expect(options).toEqual([ + { id: 'generated-id_0', label: 'value1' }, + { id: 'generated-id_1', label: '' }, + ]); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx new file mode 100644 index 0000000000000..df61e6e3dcc05 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { + htmlIdGenerator, + EuiCheckboxGroup, + EuiFlexGroup, + EuiButtonEmpty, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface Option { + value: string; + selected: boolean; +} + +interface Props { + label: string; + options: Option[]; + showMore: boolean; + onMoreClick(): void; + onRemove(id: string): void; + onSelect(id: string): void; +} + +const getIndexFromId = (id: string) => parseInt(id.split('_')[1], 10); + +export const MultiCheckboxFacetsView: React.FC = ({ + label, + onMoreClick, + onRemove, + onSelect, + options, + showMore, +}) => { + const getId = htmlIdGenerator(); + + const optionToCheckBoxGroupOption = (option: Option, index: number) => ({ + id: getId(String(index)), + label: + option.value || + i18n.translate( + 'xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.noValue.selectOption', + { + defaultMessage: '', + } + ), + }); + + const optionToSelectedMapReducer = ( + selectedMap: { [name: string]: boolean }, + option: Option, + index: number + ) => { + if (option.selected) { + selectedMap[getId(String(index))] = true; + } + return selectedMap; + }; + + const checkboxGroupOptions = options.map(optionToCheckBoxGroupOption); + const idToSelectedMap = options.reduce(optionToSelectedMapReducer, {}); + + const onChange = (checkboxId: string) => { + const index = getIndexFromId(checkboxId); + const option = options[index]; + if (option.selected) { + onRemove(option.value); + return; + } + onSelect(option.value); + }; + + return ( + <> + + {showMore && ( + <> + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.showMore', + { + defaultMessage: 'Show more', + } + )} + + + + )} + + ); +}; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 07befe8a26b2f..1bbf4b8033755 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7208,8 +7208,6 @@ "xpack.enterpriseSearch.appSearch.documents.search.indexingGuide": "インデックスガイドをお読みください", "xpack.enterpriseSearch.appSearch.documents.search.noResults": "「{resultSearchTerm}」の結果がありません。", "xpack.enterpriseSearch.appSearch.documents.search.placeholder": "ドキュメントのフィルター...", - "xpack.enterpriseSearch.appSearch.documents.search.recentlyUploadedAsc": "最近アップロードされたドキュメント(昇順)", - "xpack.enterpriseSearch.appSearch.documents.search.recentlyUploadedDesc": "最近アップロードされたドキュメント(降順)", "xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.ariaLabel": "1 ページに表示する結果数", "xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.show": "表示:", "xpack.enterpriseSearch.appSearch.documents.search.sortBy": "並べ替え基準", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 87af04f7dec87..51205a3420be5 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7227,8 +7227,6 @@ "xpack.enterpriseSearch.appSearch.documents.search.indexingGuide": "请阅读索引指南", "xpack.enterpriseSearch.appSearch.documents.search.noResults": "还没有匹配“{resultSearchTerm}”的结果!", "xpack.enterpriseSearch.appSearch.documents.search.placeholder": "筛选文档......", - "xpack.enterpriseSearch.appSearch.documents.search.recentlyUploadedAsc": "最近上传(升序)", - "xpack.enterpriseSearch.appSearch.documents.search.recentlyUploadedDesc": "最近上传(降序)", "xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.ariaLabel": "每页要显示的结果数", "xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.show": "显示:", "xpack.enterpriseSearch.appSearch.documents.search.sortBy": "排序依据", From 1edc7998946481358870c8bc881a604d8ba169db Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Wed, 20 Jan 2021 14:21:38 -0600 Subject: [PATCH 27/83] [index patterns] improve developer docs (#86416) * add index pattern docs --- ...blic.iindexpattern.getformatterforfield.md | 2 + ...lugin-plugins-data-public.iindexpattern.md | 6 +- ...-plugins-data-public.iindexpattern.type.md | 2 + ...ins-data-public.indexpattern.fieldattrs.md | 11 --- ...s-data-public.indexpattern.intervalname.md | 5 + ...plugin-plugins-data-public.indexpattern.md | 9 +- ...plugins-data-public.indexpattern.tospec.md | 2 + ...n-plugins-data-public.indexpattern.type.md | 2 + ...ugins-data-public.indexpattern.typemeta.md | 2 + ...lugins-data-public.indexpattern.version.md | 2 + ...gins-data-public.indexpatternattributes.md | 2 + ...plugins-data-public.indexpatternspec.id.md | 2 + ...ta-public.indexpatternspec.intervalname.md | 5 + ...in-plugins-data-public.indexpatternspec.md | 6 +- ...ns-data-public.indexpatternspec.version.md | 2 + ...data-public.indexpatternsservice.create.md | 2 + ...s-data-public.indexpatternsservice.find.md | 2 + ...lugins-data-public.indexpatternsservice.md | 2 +- .../kibana-plugin-plugins-data-public.md | 6 +- ...ins-data-server.indexpattern.fieldattrs.md | 11 --- ...s-data-server.indexpattern.intervalname.md | 5 + ...plugin-plugins-data-server.indexpattern.md | 9 +- ...plugins-data-server.indexpattern.tospec.md | 2 + ...n-plugins-data-server.indexpattern.type.md | 2 + ...ugins-data-server.indexpattern.typemeta.md | 2 + ...lugins-data-server.indexpattern.version.md | 2 + ...gins-data-server.indexpatternattributes.md | 2 + .../kibana-plugin-plugins-data-server.md | 2 +- src/plugins/data/README.mdx | 92 +++++++++++++++++-- .../index_patterns/index_pattern.ts | 20 +++- .../index_patterns/index_patterns.ts | 15 ++- .../data/common/index_patterns/types.ts | 44 +++++++++ src/plugins/data/public/public.api.md | 29 ++---- src/plugins/data/server/server.api.md | 20 +--- 34 files changed, 240 insertions(+), 89 deletions(-) delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldattrs.md delete mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldattrs.md 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 index 7466e4b9cf658..5fc29ca5031b4 100644 --- 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 @@ -4,6 +4,8 @@ ## IIndexPattern.getFormatterForField property +Look up a formatter for a given field + Signature: ```typescript 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 ba77e659f0834..3a78395b42754 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 @@ -4,6 +4,8 @@ ## IIndexPattern interface +IIndexPattern allows for an IndexPattern OR an index pattern saved object too ambiguous, should be avoided + Signature: ```typescript @@ -16,11 +18,11 @@ export interface IIndexPattern | --- | --- | --- | | [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 | | +| [getFormatterForField](./kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md) | (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat | Look up a formatter for a given field | | [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 | | -| [type](./kibana-plugin-plugins-data-public.iindexpattern.type.md) | string | | +| [type](./kibana-plugin-plugins-data-public.iindexpattern.type.md) | string | Type is used for identifying rollup indices, otherwise left undefined | ## Methods diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.type.md index ea75c20b403c0..d517163090c85 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.type.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.type.md @@ -4,6 +4,8 @@ ## IIndexPattern.type property +Type is used for identifying rollup indices, otherwise left undefined + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldattrs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldattrs.md deleted file mode 100644 index c2e0b9bb855f4..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldattrs.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) > [fieldAttrs](./kibana-plugin-plugins-data-public.indexpattern.fieldattrs.md) - -## IndexPattern.fieldAttrs property - -Signature: - -```typescript -fieldAttrs: FieldAttrs; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.intervalname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.intervalname.md index 762b4a37bfd28..81e7fb9c1d57b 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.intervalname.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.intervalname.md @@ -4,6 +4,11 @@ ## IndexPattern.intervalName property +> Warning: This API is now obsolete. +> +> Deprecated. used by time range index patterns +> + Signature: ```typescript 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 b640ef1b89606..872e23e450f88 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 @@ -22,7 +22,6 @@ export declare class IndexPattern implements IIndexPattern | --- | --- | --- | --- | | [allowNoIndex](./kibana-plugin-plugins-data-public.indexpattern.allownoindex.md) | | boolean | prevents errors when index pattern exists before indices | | [deleteFieldFormat](./kibana-plugin-plugins-data-public.indexpattern.deletefieldformat.md) | | (fieldName: string) => void | | -| [fieldAttrs](./kibana-plugin-plugins-data-public.indexpattern.fieldattrs.md) | | FieldAttrs | | | [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> | | @@ -38,9 +37,9 @@ export declare class IndexPattern implements IIndexPattern | [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 | | -| [type](./kibana-plugin-plugins-data-public.indexpattern.type.md) | | string | undefined | | -| [typeMeta](./kibana-plugin-plugins-data-public.indexpattern.typemeta.md) | | TypeMeta | | -| [version](./kibana-plugin-plugins-data-public.indexpattern.version.md) | | string | undefined | | +| [type](./kibana-plugin-plugins-data-public.indexpattern.type.md) | | string | undefined | Type is used to identify rollup index patterns | +| [typeMeta](./kibana-plugin-plugins-data-public.indexpattern.typemeta.md) | | TypeMeta | Only used by rollup indices, used by rollup specific endpoint to load field list | +| [version](./kibana-plugin-plugins-data-public.indexpattern.version.md) | | string | undefined | SavedObject version | ## Methods @@ -63,5 +62,5 @@ export declare class IndexPattern implements IIndexPattern | [setFieldAttrs(fieldName, attrName, value)](./kibana-plugin-plugins-data-public.indexpattern.setfieldattrs.md) | | | | [setFieldCount(fieldName, count)](./kibana-plugin-plugins-data-public.indexpattern.setfieldcount.md) | | | | [setFieldCustomLabel(fieldName, customLabel)](./kibana-plugin-plugins-data-public.indexpattern.setfieldcustomlabel.md) | | | -| [toSpec()](./kibana-plugin-plugins-data-public.indexpattern.tospec.md) | | | +| [toSpec()](./kibana-plugin-plugins-data-public.indexpattern.tospec.md) | | Create static representation of index pattern | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tospec.md index d1a78eea660ce..d8153530e5c13 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tospec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tospec.md @@ -4,6 +4,8 @@ ## IndexPattern.toSpec() method +Create static representation of index pattern + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.type.md index 7a10d058b9c65..0f9572d1bad24 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.type.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.type.md @@ -4,6 +4,8 @@ ## IndexPattern.type property +Type is used to identify rollup index patterns + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.typemeta.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.typemeta.md index ea8533a8d837c..ce316ff9638ac 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.typemeta.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.typemeta.md @@ -4,6 +4,8 @@ ## IndexPattern.typeMeta property +Only used by rollup indices, used by rollup specific endpoint to load field list + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.version.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.version.md index 99d3bc4e7a04d..2083bd65e9b0a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.version.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.version.md @@ -4,6 +4,8 @@ ## IndexPattern.version property +SavedObject version + Signature: ```typescript 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 1bbede5658942..297bfa855f0eb 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,6 +4,8 @@ ## IndexPatternAttributes interface +Interface for an index pattern saved object + Signature: ```typescript 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 index 55eadbf36c660..807f777841685 100644 --- 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 @@ -4,6 +4,8 @@ ## IndexPatternSpec.id property +saved object id + Signature: ```typescript 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 index 98748661256da..90c5ee5666231 100644 --- 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 @@ -4,6 +4,11 @@ ## IndexPatternSpec.intervalName property +> Warning: This API is now obsolete. +> +> Deprecated. Was used by time range based index patterns +> + Signature: ```typescript 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 index 9357ad7d5077e..c0fa165cfb115 100644 --- 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 @@ -4,6 +4,8 @@ ## IndexPatternSpec interface +Static index pattern format Serialized data object, representing index pattern attributes and state + Signature: ```typescript @@ -18,12 +20,12 @@ export interface IndexPatternSpec | [fieldAttrs](./kibana-plugin-plugins-data-public.indexpatternspec.fieldattrs.md) | FieldAttrs | | | [fieldFormats](./kibana-plugin-plugins-data-public.indexpatternspec.fieldformats.md) | Record<string, SerializedFieldFormat> | | | [fields](./kibana-plugin-plugins-data-public.indexpatternspec.fields.md) | IndexPatternFieldMap | | -| [id](./kibana-plugin-plugins-data-public.indexpatternspec.id.md) | string | | +| [id](./kibana-plugin-plugins-data-public.indexpatternspec.id.md) | string | saved object id | | [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 | | +| [version](./kibana-plugin-plugins-data-public.indexpatternspec.version.md) | string | saved object version string | 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 index 43f7cf0226fb0..60975b94e9633 100644 --- 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 @@ -4,6 +4,8 @@ ## IndexPatternSpec.version property +saved object version string + Signature: ```typescript 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 index d7152ba617cc6..c8e845eb1d1bf 100644 --- 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 @@ -23,3 +23,5 @@ create(spec: IndexPatternSpec, skipFetchFields?: boolean): Promise `Promise` +IndexPattern + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.find.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.find.md index f642965c5da80..929322fc4794c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.find.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.find.md @@ -4,6 +4,8 @@ ## IndexPatternsService.find property +Find and load index patterns by title + Signature: ```typescript 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 index 30ce1fa1de386..1511de18cab51 100644 --- 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 @@ -23,7 +23,7 @@ export declare class IndexPatternsService | [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[], fieldAttrs?: FieldAttrs | undefined) => Record<string, FieldSpec> | Converts field array to map | -| [find](./kibana-plugin-plugins-data-public.indexpatternsservice.find.md) | | (search: string, size?: number) => Promise<IndexPattern[]> | | +| [find](./kibana-plugin-plugins-data-public.indexpatternsservice.find.md) | | (search: string, size?: number) => Promise<IndexPattern[]> | Find and load index patterns by title | | [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 | 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 6a3e7662e59bc..65a722868b37f 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 @@ -65,12 +65,12 @@ | [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.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) | | +| [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) | IIndexPattern allows for an IndexPattern OR an index pattern saved object too ambiguous, should be avoided | | [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) | | -| [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) | | +| [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) | Interface for an index pattern saved object | +| [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) | Static index pattern format Serialized data object, representing index pattern attributes and state | | [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. | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldattrs.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldattrs.md deleted file mode 100644 index c8bad55dee2e4..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldattrs.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [fieldAttrs](./kibana-plugin-plugins-data-server.indexpattern.fieldattrs.md) - -## IndexPattern.fieldAttrs property - -Signature: - -```typescript -fieldAttrs: FieldAttrs; -``` 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 index caaa6929235f8..c144520075790 100644 --- 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 @@ -4,6 +4,11 @@ ## IndexPattern.intervalName property +> Warning: This API is now obsolete. +> +> Deprecated. used by time range index patterns +> + Signature: ```typescript 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 index 54f020e57cf4a..70c37ba1b3926 100644 --- 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 @@ -22,7 +22,6 @@ export declare class IndexPattern implements IIndexPattern | --- | --- | --- | --- | | [allowNoIndex](./kibana-plugin-plugins-data-server.indexpattern.allownoindex.md) | | boolean | prevents errors when index pattern exists before indices | | [deleteFieldFormat](./kibana-plugin-plugins-data-server.indexpattern.deletefieldformat.md) | | (fieldName: string) => void | | -| [fieldAttrs](./kibana-plugin-plugins-data-server.indexpattern.fieldattrs.md) | | FieldAttrs | | | [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> | | @@ -38,9 +37,9 @@ export declare class IndexPattern implements IIndexPattern | [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 | | +| [type](./kibana-plugin-plugins-data-server.indexpattern.type.md) | | string | undefined | Type is used to identify rollup index patterns | +| [typeMeta](./kibana-plugin-plugins-data-server.indexpattern.typemeta.md) | | TypeMeta | Only used by rollup indices, used by rollup specific endpoint to load field list | +| [version](./kibana-plugin-plugins-data-server.indexpattern.version.md) | | string | undefined | SavedObject version | ## Methods @@ -63,5 +62,5 @@ export declare class IndexPattern implements IIndexPattern | [setFieldAttrs(fieldName, attrName, value)](./kibana-plugin-plugins-data-server.indexpattern.setfieldattrs.md) | | | | [setFieldCount(fieldName, count)](./kibana-plugin-plugins-data-server.indexpattern.setfieldcount.md) | | | | [setFieldCustomLabel(fieldName, customLabel)](./kibana-plugin-plugins-data-server.indexpattern.setfieldcustomlabel.md) | | | -| [toSpec()](./kibana-plugin-plugins-data-server.indexpattern.tospec.md) | | | +| [toSpec()](./kibana-plugin-plugins-data-server.indexpattern.tospec.md) | | Create static representation of index pattern | 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 index 5d76b8f00853b..7c3c392cf6df3 100644 --- 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 @@ -4,6 +4,8 @@ ## IndexPattern.toSpec() method +Create static representation of index pattern + Signature: ```typescript 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 index 01154ab5444d1..cc64e413ef4c8 100644 --- 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 @@ -4,6 +4,8 @@ ## IndexPattern.type property +Type is used to identify rollup index patterns + Signature: ```typescript 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 index b16bcec404d97..b759900a186ca 100644 --- 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 @@ -4,6 +4,8 @@ ## IndexPattern.typeMeta property +Only used by rollup indices, used by rollup specific endpoint to load field list + Signature: ```typescript 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 index e4297d8389111..583a0c5ab6c5b 100644 --- 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 @@ -4,6 +4,8 @@ ## IndexPattern.version property +SavedObject version + Signature: ```typescript 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 b9b9f955c7ab5..bfc7f65425f9c 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,6 +4,8 @@ ## IndexPatternAttributes interface +Interface for an index pattern saved object + Signature: ```typescript 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 1b4cf5585cb3e..e6cb5accb9e31 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 @@ -51,7 +51,7 @@ | [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) | | | [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) | | -| [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | | +| [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | Interface for an index pattern saved object | | [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) | | | [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) | | | [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | | diff --git a/src/plugins/data/README.mdx b/src/plugins/data/README.mdx index 2448d5f22ced2..145aaa64fa3ad 100644 --- a/src/plugins/data/README.mdx +++ b/src/plugins/data/README.mdx @@ -20,7 +20,7 @@ It is wired into the `TopNavMenu` component, but can be used independently. ### Fetch Query Suggestions -The `getQuerySuggestions` function helps to construct a query. +The `getQuerySuggestions` function helps to construct a query. KQL suggestion functions are registered in X-Pack, so this API does not return results in OSS. ```.ts @@ -37,7 +37,7 @@ KQL suggestion functions are registered in X-Pack, so this API does not return r ### Fetch Value Suggestions The `getValueSuggestions` function returns suggestions for field values. -This is helpful when you want to provide a user with options, for example when constructing a filter. +This is helpful when you want to provide a user with options, for example when constructing a filter. ```.ts @@ -56,7 +56,81 @@ Coming soon. ## Index Patterns -Coming soon. +The Index Patterns API provides a consistent method of structuring and formatting documents +and field lists across the various Kibana apps. Its typically used in conjunction with +SearchSource for composing queries. + +### Index Patterns API + +- Get list of index patterns +- Get default index pattern and examine fields +- Get index pattern by id +- Find index pattern by title +- Create index pattern +- Create index pattern and save it +- Modify index pattern and save it +- Delete index pattern + +#### Get list of index pattern titles and ids + +``` +const idsAndTitles = await data.indexPatterns.getIdsWithTitle(); +idsAndTitles.forEach(({id, title}) => console.log(`Index pattern id: ${id} title: ${title}`)); +``` + +#### Get default index pattern and examine fields + +``` +const defaultIndexPattern = await data.indexPatterns.getDefault(); +defaultIndexPattern.fields.forEach(({name}) => { console.log(name); }) +``` + +#### Get index pattern by id + +``` +const id = 'xxxxxx-xxx-xxxxxx'; +const indexPattern = await data.indexPatterns.get(id); +``` + +#### Find index pattern by title + +``` +const title = 'kibana-*'; +const [indexPattern] = await data.indexPatterns.find(title); +``` + +#### Create index pattern + +``` +const indexPattern = await data.indexPatterns.create({ title: 'kibana-*' }); +``` + +#### Create index pattern and save it immediately + +``` +const indexPattern = await data.indexPatterns.createAndSave({ title: 'kibana-*' }); +``` + +#### Create index pattern, modify, and save + +``` +const indexPattern = await data.indexPatterns.create({ title: 'kibana-*' }); +indexPattern.setFieldCustomLabel('customer_name', 'Customer Name'); +data.indexPatterns.createSavedObject(indexPattern); +``` + +#### Modify index pattern and save it + +``` +indexPattern.setFieldCustomLabel('customer_name', 'Customer Name'); +await data.indexPatterns.updateSavedObject(indexPattern); +``` + +#### Delete index pattern + +``` +await data.indexPatterns.delete(indexPatternId); +``` ### Index Patterns HTTP API @@ -79,7 +153,7 @@ Index patterns provide Rest-like HTTP CRUD+ API with the following endpoints: ### Index Patterns API -Index Patterns CURD API allows you to create, retrieve and delete index patterns. I also +Index Patterns REST API allows you to create, retrieve and delete index patterns. I also exposes an update endpoint which allows you to update specific fields without changing the rest of the index pattern object. @@ -146,7 +220,7 @@ The endpoint returns the created index pattern object. #### Fetch an index pattern by ID -Retrieve and index pattern by its ID. +Retrieve an index pattern by its ID. ``` GET /api/index_patterns/index_pattern/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx @@ -477,7 +551,7 @@ It contains sub-services for each of those configurations: // Constuct the query portion of the search request const query = data.query.getEsQuery(indexPattern); - + // Construct a request const request = { params: { @@ -522,7 +596,7 @@ The `SearchSource` API is a convenient way to construct and run an Elasticsearch #### Default Search Strategy One benefit of using the low-level search API, is partial response support in X-Pack, allowing for a better and more responsive user experience. -In OSS only the final result is returned. +In OSS only the final result is returned. ```.ts import { isCompleteResponse } from '../plugins/data/public'; @@ -538,8 +612,8 @@ In OSS only the final result is returned. } }, error: (e: Error) => { - // Show customized toast notifications. - // You may choose to handle errors differently if you prefer. + // Show customized toast notifications. + // You may choose to handle errors differently if you prefer. data.search.showError(e); }, }); 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 ec665a80cbe0b..452c663d96716 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 @@ -43,10 +43,20 @@ export class IndexPattern implements IIndexPattern { public id?: string; public title: string = ''; public fieldFormatMap: Record; + /** + * Only used by rollup indices, used by rollup specific endpoint to load field list + */ public typeMeta?: TypeMeta; public fields: IIndexPatternFieldList & { toSpec: () => IndexPatternFieldMap }; public timeFieldName: string | undefined; + /** + * @deprecated + * Deprecated. used by time range index patterns + */ public intervalName: string | undefined; + /** + * Type is used to identify rollup index patterns + */ public type: string | undefined; public formatHit: { (hit: Record, type?: string): any; @@ -55,14 +65,15 @@ export class IndexPattern implements IIndexPattern { public formatField: FormatFieldFn; public flattenHit: (hit: Record, deep?: boolean) => Record; public metaFields: string[]; - // savedObject version + /** + * SavedObject version + */ public version: string | undefined; public sourceFilters?: SourceFilter[]; private originalSavedObjectBody: SavedObjectBody = {}; private shortDotsEnable: boolean = false; private fieldFormats: FieldFormatsStartCommon; - // make private once manual field refresh is removed - public fieldAttrs: FieldAttrs; + private fieldAttrs: FieldAttrs; /** * prevents errors when index pattern exists before indices */ @@ -184,6 +195,9 @@ export class IndexPattern implements IIndexPattern { }; } + /** + * Create static representation of index pattern + */ public toSpec(): IndexPatternSpec { return { id: this.id, 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 4d5e666a70c0d..80cb8a55fa0a0 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 @@ -127,6 +127,12 @@ export class IndexPatternsService { return this.savedObjectsCache.map((obj) => obj?.attributes?.title); }; + /** + * Find and load index patterns by title + * @param search + * @param size + * @returns IndexPattern[] + */ find = async (search: string, size: number = 10): Promise => { const savedObjects = await this.savedObjectsClient.find({ type: 'index-pattern', @@ -206,6 +212,7 @@ export class IndexPatternsService { /** * Get field list by providing { pattern } * @param options + * @returns FieldSpec[] */ getFieldsForWildcard = async (options: GetFieldsOptions) => { const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); @@ -221,6 +228,7 @@ export class IndexPatternsService { /** * Get field list by providing an index patttern (or spec) * @param options + * @returns FieldSpec[] */ getFieldsForIndexPattern = async ( indexPattern: IndexPattern | IndexPatternSpec, @@ -266,6 +274,7 @@ export class IndexPatternsService { * @param id * @param title * @param options + * @returns Record */ private refreshFieldSpecMap = async ( fields: IndexPatternFieldMap, @@ -307,7 +316,9 @@ export class IndexPatternsService { /** * Converts field array to map - * @param fields + * @param fields: FieldSpec[] + * @param fieldAttrs: FieldAttrs + * @returns Record */ fieldArrayToMap = (fields: FieldSpec[], fieldAttrs?: FieldAttrs) => fields.reduce((collector, field) => { @@ -322,6 +333,7 @@ export class IndexPatternsService { /** * Converts index pattern saved object to index pattern spec * @param savedObject + * @returns IndexPatternSpec */ savedObjectToSpec = (savedObject: SavedObject): IndexPatternSpec => { @@ -442,6 +454,7 @@ export class IndexPatternsService { * Create a new index pattern instance * @param spec * @param skipFetchFields + * @returns IndexPattern */ async create(spec: IndexPatternSpec, skipFetchFields = false): Promise { const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 77c251ada7b21..9f9a26604a0e5 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -15,19 +15,32 @@ import { KBN_FIELD_TYPES, IndexPatternField, FieldFormat } from '..'; export type FieldFormatMap = Record; +/** + * IIndexPattern allows for an IndexPattern OR an index pattern saved object + * too ambiguous, should be avoided + */ export interface IIndexPattern { fields: IFieldType[]; title: string; id?: string; + /** + * Type is used for identifying rollup indices, otherwise left undefined + */ type?: string; timeFieldName?: string; getTimeField?(): IFieldType | undefined; fieldFormatMap?: Record | undefined>; + /** + * Look up a formatter for a given field + */ getFormatterForField?: ( field: IndexPatternField | IndexPatternField['spec'] | IFieldType ) => FieldFormat; } +/** + * Interface for an index pattern saved object + */ export interface IndexPatternAttributes { type: string; fields: string; @@ -44,6 +57,10 @@ export interface IndexPatternAttributes { allowNoIndex?: boolean; } +/** + * @intenal + * Storage of field attributes. Necessary since the field list isn't saved. + */ export interface FieldAttrs { [key: string]: FieldAttrSet; } @@ -153,9 +170,22 @@ export interface FieldSpecExportFmt { indexed?: boolean; } +/** + * Serialized version of IndexPatternField + */ export interface FieldSpec { + /** + * Popularity count is used by discover + */ count?: number; + /** + * Scripted field painless script + */ script?: string; + /** + * Scripted field langauge + * Painless is the only valid scripted field language + */ lang?: string; conflictDescriptions?: Record; format?: SerializedFieldFormat; @@ -175,10 +205,24 @@ export interface FieldSpec { export type IndexPatternFieldMap = Record; +/** + * Static index pattern format + * Serialized data object, representing index pattern attributes and state + */ export interface IndexPatternSpec { + /** + * saved object id + */ id?: string; + /** + * saved object version string + */ version?: string; title?: string; + /** + * @deprecated + * Deprecated. Was used by time range based index patterns + */ intervalName?: string; timeFieldName?: string; sourceFilters?: SourceFilter[]; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index e521e468d14a4..34b4dc9116302 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1176,7 +1176,7 @@ export interface IFieldType { // Warning: (ae-missing-release-tag) "IIndexPattern" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export interface IIndexPattern { // Warning: (ae-forgotten-export) The symbol "SerializedFieldFormat" needs to be exported by the entry point index.d.ts // @@ -1184,7 +1184,6 @@ export interface IIndexPattern { fieldFormatMap?: Record | undefined>; // (undocumented) fields: IFieldType[]; - // (undocumented) getFormatterForField?: (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat; // (undocumented) getTimeField?(): IFieldType | undefined; @@ -1194,7 +1193,6 @@ export interface IIndexPattern { timeFieldName?: string; // (undocumented) title: string; - // (undocumented) type?: string; } @@ -1263,10 +1261,6 @@ export class IndexPattern implements IIndexPattern { readonly allowNoIndex: boolean; // (undocumented) readonly deleteFieldFormat: (fieldName: string) => void; - // Warning: (ae-forgotten-export) The symbol "FieldAttrs" needs to be exported by the entry point index.d.ts - // - // (undocumented) - fieldAttrs: FieldAttrs; // (undocumented) fieldFormatMap: Record; // (undocumented) @@ -1342,7 +1336,7 @@ export class IndexPattern implements IIndexPattern { getTimeField(): IndexPatternField | undefined; // (undocumented) id?: string; - // (undocumented) + // @deprecated (undocumented) intervalName: string | undefined; // (undocumented) isTimeBased(): boolean; @@ -1368,13 +1362,9 @@ export class IndexPattern implements IIndexPattern { timeFieldName: string | undefined; // (undocumented) title: string; - // (undocumented) toSpec(): IndexPatternSpec; - // (undocumented) type: string | undefined; - // (undocumented) typeMeta?: IndexPatternTypeMeta; - // (undocumented) version: string | undefined; } @@ -1392,7 +1382,7 @@ export type IndexPatternAggRestrictions = Record, 'isLo // 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) +// @public export interface IndexPatternSpec { // (undocumented) allowNoIndex?: boolean; + // Warning: (ae-forgotten-export) The symbol "FieldAttrs" needs to be exported by the entry point index.d.ts + // // (undocumented) fieldAttrs?: FieldAttrs; // (undocumented) fieldFormats?: Record; // (undocumented) fields?: IndexPatternFieldMap; - // (undocumented) id?: string; - // (undocumented) + // @deprecated (undocumented) intervalName?: string; // (undocumented) sourceFilters?: SourceFilter[]; @@ -1547,7 +1538,6 @@ export interface IndexPatternSpec { type?: string; // (undocumented) typeMeta?: IndexPatternTypeMeta; - // (undocumented) version?: string; } @@ -1567,7 +1557,6 @@ export class IndexPatternsService { // (undocumented) ensureDefaultIndexPattern: EnsureDefaultIndexPattern; fieldArrayToMap: (fields: FieldSpec[], fieldAttrs?: FieldAttrs | undefined) => Record; - // (undocumented) find: (search: string, size?: number) => Promise; get: (id: string) => Promise; // Warning: (ae-forgotten-export) The symbol "IndexPatternSavedObjectAttrs" needs to be exported by the entry point index.d.ts @@ -2583,8 +2572,8 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/meta_filter.ts:43: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:22: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:20: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:53:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:122:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:63:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:133:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:139:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/search_source/search_source.ts:186:7 - (ae-forgotten-export) The symbol "SearchFieldValue" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:56:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index ccb40c1ea4b80..15594dc80c888 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -691,10 +691,6 @@ export class IndexPattern implements IIndexPattern { readonly allowNoIndex: boolean; // (undocumented) readonly deleteFieldFormat: (fieldName: string) => void; - // Warning: (ae-forgotten-export) The symbol "FieldAttrs" needs to be exported by the entry point index.d.ts - // - // (undocumented) - fieldAttrs: FieldAttrs; // (undocumented) fieldFormatMap: Record; // Warning: (ae-forgotten-export) The symbol "IIndexPatternFieldList" needs to be exported by the entry point index.d.ts @@ -774,7 +770,7 @@ export class IndexPattern implements IIndexPattern { getTimeField(): IndexPatternField | undefined; // (undocumented) id?: string; - // (undocumented) + // @deprecated (undocumented) intervalName: string | undefined; // (undocumented) isTimeBased(): boolean; @@ -803,22 +799,16 @@ export class IndexPattern implements IIndexPattern { // (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) version: string | undefined; } // Warning: (ae-missing-release-tag) "IndexPatternAttributes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export interface IndexPatternAttributes { allowNoIndex?: boolean; // (undocumented) @@ -1390,9 +1380,9 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // // src/plugins/data/common/es_query/filters/meta_filter.ts:42: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:43:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:47: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:53:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:122:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:50: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:63:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:133:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:29:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:29:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:46:23 - (ae-forgotten-export) The symbol "datatableToCSV" needs to be exported by the entry point index.d.ts From 954c8870069252dfcea77265aeecbf360652a8a4 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Wed, 20 Jan 2021 15:25:45 -0500 Subject: [PATCH 28/83] [Uptime] waterfall add fallback support for uncommon mime types (#88691) * uptime waterfall add fallback support for uncommon mime types * update data_formatting test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../waterfall/data_formatting.test.ts | 46 ++++++++++++++++++- .../step_detail/waterfall/data_formatting.ts | 2 +- .../synthetics/step_detail/waterfall/types.ts | 2 + 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts index a58927dfbd12f..5c0b36874004a 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts @@ -5,7 +5,8 @@ */ import { colourPalette, getSeriesAndDomain } from './data_formatting'; -import { NetworkItems } from './types'; +import { NetworkItems, MimeType } from './types'; +import { WaterfallDataEntry } from '../../waterfall/types'; describe('Palettes', () => { it('A colour palette comprising timing and mime type colours is correctly generated', () => { @@ -136,6 +137,31 @@ describe('getSeriesAndDomain', () => { }, ]; + const networkItemsWithUncommonMimeType: NetworkItems = [ + { + timestamp: '2021-01-05T19:22:28.928Z', + method: 'GET', + url: 'https://unpkg.com/director@1.2.8/build/director.js', + status: 200, + mimeType: 'application/x-javascript', + requestSentTime: 18098833.537, + requestStartTime: 18098837.233999997, + loadEndTime: 18098977.648000002, + timings: { + blocked: 84.54599999822676, + receive: 3.068000001803739, + queueing: 3.69700000010198, + proxy: -1, + total: 144.1110000014305, + wait: 52.56100000042352, + connect: -1, + send: 0.2390000008745119, + ssl: -1, + dns: -1, + }, + }, + ]; + it('formats timings', () => { const actual = getSeriesAndDomain(networkItems); expect(actual).toMatchInlineSnapshot(` @@ -456,4 +482,22 @@ describe('getSeriesAndDomain', () => { } `); }); + + it('handles formatting when mime type is not mapped to a specific mime type bucket', () => { + const actual = getSeriesAndDomain(networkItemsWithUncommonMimeType); + const { series } = actual; + /* verify that raw mime type appears in the tooltip config and that + * the colour is mapped to mime type other */ + const contentDownloadedingConfigItem = series.find((item: WaterfallDataEntry) => { + const { tooltipProps } = item.config; + if (tooltipProps && typeof tooltipProps.value === 'string') { + return ( + tooltipProps.value.includes('application/x-javascript') && + tooltipProps.colour === colourPalette[MimeType.Other] + ); + } + return false; + }); + expect(contentDownloadedingConfigItem).toBeDefined(); + }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts index 43fa93fa5f6f2..5e59026fd65f8 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts @@ -50,7 +50,7 @@ const getFriendlyTooltipValue = ({ let label = FriendlyTimingLabels[timing]; if (timing === Timings.Receive && mimeType) { const formattedMimeType: MimeType = MimeTypesMap[mimeType]; - label += ` (${FriendlyMimetypeLabels[formattedMimeType]})`; + label += ` (${FriendlyMimetypeLabels[formattedMimeType] || mimeType})`; } return `${label}: ${formatValueForDisplay(value)}ms`; }; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts index 738929741ddaf..137c0767a83e6 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts @@ -111,6 +111,7 @@ export const FriendlyMimetypeLabels = { export const MimeTypesMap: Record = { 'text/html': MimeType.Html, 'application/javascript': MimeType.Script, + 'application/json': MimeType.Script, 'text/javascript': MimeType.Script, 'text/css': MimeType.Stylesheet, // Images @@ -130,6 +131,7 @@ export const MimeTypesMap: Record = { 'audio/x-pn-wav': MimeType.Media, 'audio/webm': MimeType.Media, 'video/webm': MimeType.Media, + 'video/mp4': MimeType.Media, 'audio/ogg': MimeType.Media, 'video/ogg': MimeType.Media, 'application/ogg': MimeType.Media, From da8abdaf754e954b442ed7b81cd3e2fa62a0bf2b Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 20 Jan 2021 14:29:47 -0700 Subject: [PATCH 29/83] [Maps] fix tags changed in Maps Save dialog don't refresh until the map is reopened (#88849) * [Maps] fix tags changed in Maps Save dialog don't refresh until the map is reopened * only set tags if newTags are provided --- .../maps/public/routes/map_page/saved_map/saved_map.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts index f3c3ec528c034..27fd78980710f 100644 --- a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts @@ -292,8 +292,12 @@ export class SavedMap { const prevTitle = this._attributes.title; const prevDescription = this._attributes.description; + const prevTags = this._tags; this._attributes.title = newTitle; this._attributes.description = newDescription; + if (newTags) { + this._tags = newTags; + } this._syncAttributesWithStore(); let updatedMapEmbeddableInput: MapEmbeddableInput; @@ -316,6 +320,7 @@ export class SavedMap { // Error toast displayed by wrapAttributes this._attributes.title = prevTitle; this._attributes.description = prevDescription; + this._tags = prevTags; return; } From 82c1501924dd0cb3d11ef5fbc2921498d48082a1 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Wed, 20 Jan 2021 16:55:29 -0500 Subject: [PATCH 30/83] [CI] [TeamCity] Move PR commit status publishing gate to accommodate PR bot (#88911) --- .teamcity/src/builds/PullRequestCi.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.teamcity/src/builds/PullRequestCi.kt b/.teamcity/src/builds/PullRequestCi.kt index c38591fe850ac..997cf1771cc8d 100644 --- a/.teamcity/src/builds/PullRequestCi.kt +++ b/.teamcity/src/builds/PullRequestCi.kt @@ -63,13 +63,15 @@ object PullRequestCi : BuildType({ } features { - commitStatusPublisher { - enabled = isReportingEnabled() - vcsRootExtId = "${Kibana.id}" - publisher = github { - githubUrl = "https://api.github.com" - authType = personalToken { - token = "credentialsJSON:07d22002-12de-4627-91c3-672bdb23b55b" + if(isReportingEnabled()) { + commitStatusPublisher { + enabled = true + vcsRootExtId = "${Kibana.id}" + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:07d22002-12de-4627-91c3-672bdb23b55b" + } } } } From 25f16db4d9791a8943b386cccb8680d70bc6bf54 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Wed, 20 Jan 2021 17:39:21 -0500 Subject: [PATCH 31/83] Sharing saved objects, phase 2 (#80945) --- docs/api/saved-objects.asciidoc | 3 + docs/api/saved-objects/get.asciidoc | 2 +- docs/api/saved-objects/resolve.asciidoc | 130 ++ ...public.savedobject.coremigrationversion.md | 13 + .../kibana-plugin-core-public.savedobject.md | 1 + ...jectscreateoptions.coremigrationversion.md | 13 + ...n-core-public.savedobjectscreateoptions.md | 1 + ...-public.simplesavedobject._constructor_.md | 4 +- ....simplesavedobject.coremigrationversion.md | 11 + ...na-plugin-core-public.simplesavedobject.md | 3 +- .../core/server/kibana-plugin-core-server.md | 4 +- ...server.savedobject.coremigrationversion.md | 13 + .../kibana-plugin-core-server.savedobject.md | 1 + ...gin-core-server.savedobjectmigrationmap.md | 2 +- ...tsbulkcreateobject.coremigrationversion.md | 18 + ...ore-server.savedobjectsbulkcreateobject.md | 1 + ...a-plugin-core-server.savedobjectsclient.md | 1 + ...-core-server.savedobjectsclient.resolve.md | 26 + ...jectscreateoptions.coremigrationversion.md | 18 + ...n-core-server.savedobjectscreateoptions.md | 1 + ...e-server.savedobjectsrawdocparseoptions.md | 20 + ...tsrawdocparseoptions.namespacetreatment.md | 15 + ...ugin-core-server.savedobjectsrepository.md | 1 + ...e-server.savedobjectsrepository.resolve.md | 28 + ...core-server.savedobjectsresolveresponse.md | 20 + ...ver.savedobjectsresolveresponse.outcome.md | 15 + ...avedobjectsresolveresponse.saved_object.md | 11 + ...sserializer.generaterawlegacyurlaliasid.md | 26 + ...savedobjectsserializer.israwsavedobject.md | 5 +- ...ugin-core-server.savedobjectsserializer.md | 5 +- ...savedobjectsserializer.rawtosavedobject.md | 3 +- ...type.converttomultinamespacetypeversion.md | 42 + ...ana-plugin-core-server.savedobjectstype.md | 22 + ...plugin-plugins-data-server.plugin.start.md | 4 +- docs/user/security/audit-logging.asciidoc | 4 + src/core/public/public.api.md | 6 +- .../saved_objects/saved_objects_client.ts | 2 + .../saved_objects/simple_saved_object.ts | 14 +- .../core_usage_stats_client.mock.ts | 1 + .../core_usage_stats_client.test.ts | 76 + .../core_usage_stats_client.ts | 6 + src/core/server/core_usage_data/types.ts | 7 + src/core/server/index.ts | 2 + src/core/server/saved_objects/index.ts | 1 + .../migrations/core/__mocks__/index.ts | 13 + .../build_active_mappings.test.ts.snap | 8 + .../migrations/core/build_active_mappings.ts | 3 + .../migrations/core/document_migrator.test.ts | 1665 +++++++++++------ .../migrations/core/document_migrator.ts | 551 +++++- .../migrations/core/elastic_index.test.ts | 21 +- .../migrations/core/elastic_index.ts | 32 +- .../migrations/core/index_migrator.test.ts | 27 +- .../migrations/core/index_migrator.ts | 7 +- .../migrations/core/migrate_raw_docs.test.ts | 84 +- .../migrations/core/migrate_raw_docs.ts | 23 +- .../migrations/core/migration_context.ts | 3 + .../kibana_migrator.test.ts.snap | 4 + .../migrations/kibana/kibana_migrator.ts | 4 +- .../server/saved_objects/migrations/types.ts | 2 +- .../saved_objects/object_types/constants.ts | 12 + .../saved_objects/object_types/index.ts | 11 + .../object_types/registration.test.ts | 25 + .../object_types/registration.ts | 29 + .../saved_objects/object_types/types.ts | 19 + .../saved_objects/routes/bulk_create.ts | 1 + .../server/saved_objects/routes/create.ts | 18 +- src/core/server/saved_objects/routes/index.ts | 2 + .../routes/integration_tests/resolve.test.ts | 91 + .../server/saved_objects/routes/resolve.ts | 38 + .../saved_objects_service.test.ts | 13 + .../saved_objects/saved_objects_service.ts | 3 + .../saved_objects/serialization/index.ts | 1 + .../serialization/serializer.test.ts | 215 +++ .../saved_objects/serialization/serializer.ts | 132 +- .../saved_objects/serialization/types.ts | 17 + .../service/lib/included_fields.test.ts | 4 +- .../service/lib/included_fields.ts | 1 + .../service/lib/repository.mock.ts | 1 + .../service/lib/repository.test.js | 235 ++- .../saved_objects/service/lib/repository.ts | 191 +- .../service/saved_objects_client.mock.ts | 1 + .../service/saved_objects_client.test.js | 16 + .../service/saved_objects_client.ts | 53 + src/core/server/saved_objects/types.ts | 35 + src/core/server/server.api.md | 37 +- src/core/types/saved_objects.ts | 2 + src/plugins/data/server/server.api.md | 2 +- .../collectors/core/core_usage_collector.ts | 7 + src/plugins/telemetry/schema/oss_plugins.json | 21 + .../apis/saved_objects/bulk_create.ts | 11 + .../apis/saved_objects/bulk_get.ts | 9 + .../apis/saved_objects/create.ts | 39 + .../apis/saved_objects/export.ts | 10 + .../apis/saved_objects/find.ts | 13 +- .../api_integration/apis/saved_objects/get.ts | 8 + .../apis/saved_objects/index.ts | 5 +- .../lib/saved_objects_test_utils.ts | 18 + .../apis/saved_objects/migrations.ts | 346 +++- .../apis/saved_objects/resolve.ts | 104 + .../apis/saved_objects_management/find.ts | 10 + ...ypted_saved_objects_client_wrapper.test.ts | 134 ++ .../encrypted_saved_objects_client_wrapper.ts | 13 + .../server/audit/audit_events.test.ts | 18 + .../security/server/audit/audit_events.ts | 3 + ...ecure_saved_objects_client_wrapper.test.ts | 77 + .../secure_saved_objects_client_wrapper.ts | 36 + .../spaces_saved_objects_client.test.ts | 31 + .../spaces_saved_objects_client.ts | 22 + .../saved_objects/spaces/data.json | 116 ++ .../saved_objects/spaces/mappings.json | 29 + .../saved_object_test_plugin/server/plugin.ts | 7 + .../common/suites/resolve.ts | 138 ++ .../security_and_spaces/apis/index.ts | 1 + .../security_and_spaces/apis/resolve.ts | 82 + .../security_only/apis/index.ts | 1 + .../security_only/apis/resolve.ts | 73 + .../spaces_only/apis/index.ts | 1 + .../spaces_only/apis/resolve.ts | 47 + 118 files changed, 4859 insertions(+), 825 deletions(-) create mode 100644 docs/api/saved-objects/resolve.asciidoc create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobject.coremigrationversion.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectscreateoptions.coremigrationversion.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.simplesavedobject.coremigrationversion.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobject.coremigrationversion.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.coremigrationversion.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.resolve.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.coremigrationversion.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsrawdocparseoptions.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsrawdocparseoptions.namespacetreatment.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.resolve.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.outcome.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.generaterawlegacyurlaliasid.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectstype.converttomultinamespacetypeversion.md create mode 100644 src/core/server/saved_objects/migrations/core/__mocks__/index.ts create mode 100644 src/core/server/saved_objects/object_types/constants.ts create mode 100644 src/core/server/saved_objects/object_types/index.ts create mode 100644 src/core/server/saved_objects/object_types/registration.test.ts create mode 100644 src/core/server/saved_objects/object_types/registration.ts create mode 100644 src/core/server/saved_objects/object_types/types.ts create mode 100644 src/core/server/saved_objects/routes/integration_tests/resolve.test.ts create mode 100644 src/core/server/saved_objects/routes/resolve.ts create mode 100644 test/api_integration/apis/saved_objects/lib/saved_objects_test_utils.ts create mode 100644 test/api_integration/apis/saved_objects/resolve.ts create mode 100644 x-pack/test/saved_object_api_integration/common/suites/resolve.ts create mode 100644 x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve.ts create mode 100644 x-pack/test/saved_object_api_integration/security_only/apis/resolve.ts create mode 100644 x-pack/test/saved_object_api_integration/spaces_only/apis/resolve.ts diff --git a/docs/api/saved-objects.asciidoc b/docs/api/saved-objects.asciidoc index 0d8ceefb47e91..ecf975134c64a 100644 --- a/docs/api/saved-objects.asciidoc +++ b/docs/api/saved-objects.asciidoc @@ -10,6 +10,8 @@ The following saved objects APIs are available: * <> to retrieve a single {kib} saved object by ID +* <> to retrieve a single {kib} saved object by ID, using any legacy URL alias if it exists + * <> to retrieve multiple {kib} saved objects by ID * <> to retrieve a paginated set of {kib} saved objects by various conditions @@ -40,4 +42,5 @@ include::saved-objects/delete.asciidoc[] include::saved-objects/export.asciidoc[] include::saved-objects/import.asciidoc[] include::saved-objects/resolve_import_errors.asciidoc[] +include::saved-objects/resolve.asciidoc[] include::saved-objects/rotate_encryption_key.asciidoc[] diff --git a/docs/api/saved-objects/get.asciidoc b/docs/api/saved-objects/get.asciidoc index 6aad9759ef5e0..4c8cd020e0286 100644 --- a/docs/api/saved-objects/get.asciidoc +++ b/docs/api/saved-objects/get.asciidoc @@ -78,7 +78,7 @@ The API returns the following: "title": "[Flights] Global Flight Dashboard", "hits": 0, "description": "Analyze mock flight data for ES-Air, Logstash Airways, Kibana Airlines and JetBeats", - "panelsJSON": "[{\"panelIndex\":\"1\",\"gridData\":{\"x\":0,\"y\":0,\"w\":32,\"h\":7,\"i\":\"1\"},\"embeddableConfig\":{},\"version\":\"6.3.0\",\"panelRefName\":\"panel_0\"},{\"panelIndex\":\"3\",\"gridData\":{\"x\":17,\"y\":7,\"w\":23,\"h\":12,\"i\":\"3\"},\"embeddableConfig\":{\"vis\":{\"colors\":{\"Average Ticket Price\":\"#0A50A1\",\"Flight Count\":\"#82B5D8\"},\"legendOpen\":false}},\"version\":\"6.3.0\",\"panelRefName\":\"panel_1\"},{\"panelIndex\":\"4\",\"gridData\":{\"x\":0,\"y\":85,\"w\":48,\"h\":15,\"i\":\"4\"},\"embeddableConfig\":{},\"version\":\"6.3.0\",\"panelRefName\":\"panel_2\"},{\"panelIndex\":\"5\",\"gridData\":{\"x\":0,\"y\":7,\"w\":17,\"h\":12,\"i\":\"5\"},\"embeddableConfig\":{\"vis\":{\"colors\":{\"ES-Air\":\"#447EBC\",\"JetBeats\":\"#65C5DB\",\"Kibana Airlines\":\"#BA43A9\",\"Logstash Airways\":\"#E5AC0E\"},\"legendOpen\":false}},\"version\":\"6.3.0\",\"panelRefName\":\"panel_3\"},{\"panelIndex\":\"6\",\"gridData\":{\"x\":24,\"y\":33,\"w\":24,\"h\":14,\"i\":\"6\"},\"embeddableConfig\":{\"vis\":{\"colors\":{\"Carrier Delay\":\"#5195CE\",\"Late Aircraft Delay\":\"#1F78C1\",\"NAS Delay\":\"#70DBED\",\"No Delay\":\"#BADFF4\",\"Security Delay\":\"#052B51\",\"Weather Delay\":\"#6ED0E0\"}}},\"version\":\"6.3.0\",\"panelRefName\":\"panel_4\"},{\"panelIndex\":\"7\",\"gridData\":{\"x\":24,\"y\":19,\"w\":24,\"h\":14,\"i\":\"7\"},\"embeddableConfig\":{},\"version\":\"6.3.0\",\"panelRefName\":\"panel_5\"},{\"panelIndex\":\"10\",\"gridData\":{\"x\":0,\"y\":35,\"w\":24,\"h\":12,\"i\":\"10\"},\"embeddableConfig\":{\"vis\":{\"colors\":{\"Count\":\"#1F78C1\"},\"legendOpen\":false}},\"version\":\"6.3.0\",\"panelRefName\":\"panel_6\"},{\"panelIndex\":\"13\",\"gridData\":{\"x\":10,\"y\":19,\"w\":14,\"h\":8,\"i\":\"13\"},\"embeddableConfig\":{\"vis\":{\"colors\":{\"Count\":\"#1F78C1\"},\"legendOpen\":false}},\"version\":\"6.3.0\",\"panelRefName\":\"panel_7\"},{\"panelIndex\":\"14\",\"gridData\":{\"x\":10,\"y\":27,\"w\":14,\"h\":8,\"i\":\"14\"},\"embeddableConfig\":{\"vis\":{\"colors\":{\"Count\":\"#1F78C1\"},\"legendOpen\":false}},\"version\":\"6.3.0\",\"panelRefName\":\"panel_8\"},{\"panelIndex\":\"18\",\"gridData\":{\"x\":24,\"y\":70,\"w\":24,\"h\":15,\"i\":\"18\"},\"embeddableConfig\":{\"mapCenter\":[27.421687059550266,15.371002131141724],\"mapZoom\":1},\"version\":\"6.3.0\",\"panelRefName\":\"panel_9\"},{\"panelIndex\":\"21\",\"gridData\":{\"x\":0,\"y\":62,\"w\":48,\"h\":8,\"i\":\"21\"},\"embeddableConfig\":{},\"version\":\"6.3.0\",\"panelRefName\":\"panel_10\"},{\"panelIndex\":\"22\",\"gridData\":{\"x\":32,\"y\":0,\"w\":16,\"h\":7,\"i\":\"22\"},\"embeddableConfig\":{},\"version\":\"6.3.0\",\"panelRefName\":\"panel_11\"},{\"panelIndex\":\"23\",\"gridData\":{\"x\":0,\"y\":70,\"w\":24,\"h\":15,\"i\":\"23\"},\"embeddableConfig\":{\"mapCenter\":[42.19556096274418,9.536742995308601e-7],\"mapZoom\":1},\"version\":\"6.3.0\",\"panelRefName\":\"panel_12\"},{\"panelIndex\":\"25\",\"gridData\":{\"x\":0,\"y\":19,\"w\":10,\"h\":8,\"i\":\"25\"},\"embeddableConfig\":{\"vis\":{\"defaultColors\":{\"0 - 50\":\"rgb(247,251,255)\",\"100 - 150\":\"rgb(107,174,214)\",\"150 - 200\":\"rgb(33,113,181)\",\"200 - 250\":\"rgb(8,48,107)\",\"50 - 100\":\"rgb(198,219,239)\"},\"legendOpen\":false}},\"version\":\"6.3.0\",\"panelRefName\":\"panel_13\"},{\"panelIndex\":\"27\",\"gridData\":{\"x\":0,\"y\":27,\"w\":10,\"h\":8,\"i\":\"27\"},\"embeddableConfig\":{\"vis\":{\"defaultColors\":{\"0 - 50\":\"rgb(247,251,255)\",\"100 - 150\":\"rgb(107,174,214)\",\"150 - 200\":\"rgb(33,113,181)\",\"200 - 250\":\"rgb(8,48,107)\",\"50 - 100\":\"rgb(198,219,239)\"},\"legendOpen\":false}},\"version\":\"6.3.0\",\"panelRefName\":\"panel_14\"},{\"panelIndex\":\"28\",\"gridData\":{\"x\":0,\"y\":47,\"w\":24,\"h\":15,\"i\":\"28\"},\"embeddableConfig\":{\"vis\":{\"defaultColors\":{\"0 -* Connection #0 to host 69c72adb58fa46c69a01afdf4a6cbfd3.us-west1.gcp.cloud.es.io left intact\n 11\":\"rgb(247,251,255)\",\"11 - 22\":\"rgb(208,225,242)\",\"22 - 33\":\"rgb(148,196,223)\",\"33 - 44\":\"rgb(74,152,201)\",\"44 - 55\":\"rgb(23,100,171)\"},\"legendOpen\":false}},\"version\":\"6.3.0\",\"panelRefName\":\"panel_15\"},{\"panelIndex\":\"29\",\"gridData\":{\"x\":40,\"y\":7,\"w\":8,\"h\":6,\"i\":\"29\"},\"embeddableConfig\":{},\"version\":\"6.3.0\",\"panelRefName\":\"panel_16\"},{\"panelIndex\":\"30\",\"gridData\":{\"x\":40,\"y\":13,\"w\":8,\"h\":6,\"i\":\"30\"},\"embeddableConfig\":{},\"version\":\"6.3.0\",\"panelRefName\":\"panel_17\"},{\"panelIndex\":\"31\",\"gridData\":{\"x\":24,\"y\":47,\"w\":24,\"h\":15,\"i\":\"31\"},\"embeddableConfig\":{},\"version\":\"6.3.0\",\"panelRefName\":\"panel_18\"}]", + "panelsJSON": "[ . . . ]", "optionsJSON": "{\"hidePanelTitles\":false,\"useMargins\":true}", "version": 1, "timeRestore": true, diff --git a/docs/api/saved-objects/resolve.asciidoc b/docs/api/saved-objects/resolve.asciidoc new file mode 100644 index 0000000000000..f2bf31bc5d9e4 --- /dev/null +++ b/docs/api/saved-objects/resolve.asciidoc @@ -0,0 +1,130 @@ +[[saved-objects-api-resolve]] +=== Resolve object API +++++ +Resolve object +++++ + +experimental[] Retrieve a single {kib} saved object by ID, using any legacy URL alias if it exists. + +Under certain circumstances, when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new +features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that +object can be retrieved via the Resolve API using either its new ID or its old ID. + +[[saved-objects-api-resolve-request]] +==== Request + +`GET :/api/saved_objects/resolve//` + +`GET :/s//api/saved_objects/resolve//` + +[[saved-objects-api-resolve-params]] +==== Path parameters + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + + +`type`:: + (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`, and `timelion-sheet`. + +`id`:: + (Required, string) The ID of the object to retrieve. + +[[saved-objects-api-resolve-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[saved-objects-api-resolve-example]] +==== Example + +Retrieve the index pattern object with the `my-pattern` ID: + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/saved_objects/resolve/index-pattern/my-pattern +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "saved_object": { + "id": "my-pattern", + "type": "index-pattern", + "version": 1, + "attributes": { + "title": "my-pattern-*" + } + }, + "outcome": "exactMatch" +} +-------------------------------------------------- + +The `outcome` field may be any of the following: + +* `"exactMatch"` -- One document exactly matched the given ID. +* `"aliasMatch"` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different than the given ID. +* `"conflict"` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID. + +Retrieve a dashboard object in the `testspace` by ID: + +[source,sh] +-------------------------------------------------- +$ curl -X GET s/testspace/api/saved_objects/resolve/dashboard/7adfa750-4c81-11e8-b3d7-01146121b73d +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "saved_object": { + "id": "7adfa750-4c81-11e8-b3d7-01146121b73d", + "type": "dashboard", + "updated_at": "2019-07-23T00:11:07.059Z", + "version": "WzQ0LDFd", + "attributes": { + "title": "[Flights] Global Flight Dashboard", + "hits": 0, + "description": "Analyze mock flight data for ES-Air, Logstash Airways, Kibana Airlines and JetBeats", + "panelsJSON": "[ . . . ]", + "optionsJSON": "{\"hidePanelTitles\":false,\"useMargins\":true}", + "version": 1, + "timeRestore": true, + "timeTo": "now", + "timeFrom": "now-24h", + "refreshInterval": { + "display": "15 minutes", + "pause": false, + "section": 2, + "value": 900000 + }, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[],\"highlightAll\":true,\"version\":true}" + } + }, + "references": [ + { + "name": "panel_0", + "type": "visualization", + "id": "aeb212e0-4c84-11e8-b3d7-01146121b73d" + }, + . . . + { + "name": "panel_18", + "type": "visualization", + "id": "ed78a660-53a0-11e8-acbd-0be0ad9d822b" + } + ], + "migrationVersion": { + "dashboard": "7.0.0" + } + }, + "outcome": "conflict" +} +-------------------------------------------------- diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobject.coremigrationversion.md b/docs/development/core/public/kibana-plugin-core-public.savedobject.coremigrationversion.md new file mode 100644 index 0000000000000..9060a5d6777fe --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobject.coremigrationversion.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObject](./kibana-plugin-core-public.savedobject.md) > [coreMigrationVersion](./kibana-plugin-core-public.savedobject.coremigrationversion.md) + +## SavedObject.coreMigrationVersion property + +A semver value that is used when upgrading objects between Kibana versions. + +Signature: + +```typescript +coreMigrationVersion?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobject.md b/docs/development/core/public/kibana-plugin-core-public.savedobject.md index eb6059747426d..9404927f94957 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobject.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobject.md @@ -15,6 +15,7 @@ export interface SavedObject | Property | Type | Description | | --- | --- | --- | | [attributes](./kibana-plugin-core-public.savedobject.attributes.md) | T | The data for a Saved Object is stored as an object in the attributes property. | +| [coreMigrationVersion](./kibana-plugin-core-public.savedobject.coremigrationversion.md) | string | A semver value that is used when upgrading objects between Kibana versions. | | [error](./kibana-plugin-core-public.savedobject.error.md) | SavedObjectError | | | [id](./kibana-plugin-core-public.savedobject.id.md) | string | The ID of this Saved Object, guaranteed to be unique for all objects of the same type | | [migrationVersion](./kibana-plugin-core-public.savedobject.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectscreateoptions.coremigrationversion.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectscreateoptions.coremigrationversion.md new file mode 100644 index 0000000000000..3c1d068f458bc --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectscreateoptions.coremigrationversion.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsCreateOptions](./kibana-plugin-core-public.savedobjectscreateoptions.md) > [coreMigrationVersion](./kibana-plugin-core-public.savedobjectscreateoptions.coremigrationversion.md) + +## SavedObjectsCreateOptions.coreMigrationVersion property + +A semver value that is used when upgrading objects between Kibana versions. + +Signature: + +```typescript +coreMigrationVersion?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectscreateoptions.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectscreateoptions.md index b1b93407d4ff1..a039b9f5b4fe4 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectscreateoptions.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectscreateoptions.md @@ -15,6 +15,7 @@ export interface SavedObjectsCreateOptions | Property | Type | Description | | --- | --- | --- | +| [coreMigrationVersion](./kibana-plugin-core-public.savedobjectscreateoptions.coremigrationversion.md) | string | A semver value that is used when upgrading objects between Kibana versions. | | [id](./kibana-plugin-core-public.savedobjectscreateoptions.id.md) | string | (Not recommended) Specify an id instead of having the saved objects service generate one for you. | | [migrationVersion](./kibana-plugin-core-public.savedobjectscreateoptions.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | | [overwrite](./kibana-plugin-core-public.savedobjectscreateoptions.overwrite.md) | boolean | If a document with the given id already exists, overwrite it's contents (default=false). | diff --git a/docs/development/core/public/kibana-plugin-core-public.simplesavedobject._constructor_.md b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject._constructor_.md index b1a4357cca7ad..8fb005421e870 100644 --- a/docs/development/core/public/kibana-plugin-core-public.simplesavedobject._constructor_.md +++ b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `SimpleSavedObject` class Signature: ```typescript -constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion }: SavedObjectType); +constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, }: SavedObjectType); ``` ## Parameters @@ -17,5 +17,5 @@ constructor(client: SavedObjectsClientContract, { id, type, version, attributes, | Parameter | Type | Description | | --- | --- | --- | | client | SavedObjectsClientContract | | -| { id, type, version, attributes, error, references, migrationVersion } | SavedObjectType<T> | | +| { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, } | SavedObjectType<T> | | diff --git a/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.coremigrationversion.md b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.coremigrationversion.md new file mode 100644 index 0000000000000..8e2217fab6eee --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.coremigrationversion.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SimpleSavedObject](./kibana-plugin-core-public.simplesavedobject.md) > [coreMigrationVersion](./kibana-plugin-core-public.simplesavedobject.coremigrationversion.md) + +## SimpleSavedObject.coreMigrationVersion property + +Signature: + +```typescript +coreMigrationVersion: SavedObjectType['coreMigrationVersion']; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.md b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.md index e9987f6d5bebb..35264a3a4cf0c 100644 --- a/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.md +++ b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.md @@ -18,7 +18,7 @@ export declare class SimpleSavedObject | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(client, { id, type, version, attributes, error, references, migrationVersion })](./kibana-plugin-core-public.simplesavedobject._constructor_.md) | | Constructs a new instance of the SimpleSavedObject class | +| [(constructor)(client, { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, })](./kibana-plugin-core-public.simplesavedobject._constructor_.md) | | Constructs a new instance of the SimpleSavedObject class | ## Properties @@ -26,6 +26,7 @@ export declare class SimpleSavedObject | --- | --- | --- | --- | | [\_version](./kibana-plugin-core-public.simplesavedobject._version.md) | | SavedObjectType<T>['version'] | | | [attributes](./kibana-plugin-core-public.simplesavedobject.attributes.md) | | T | | +| [coreMigrationVersion](./kibana-plugin-core-public.simplesavedobject.coremigrationversion.md) | | SavedObjectType<T>['coreMigrationVersion'] | | | [error](./kibana-plugin-core-public.simplesavedobject.error.md) | | SavedObjectType<T>['error'] | | | [id](./kibana-plugin-core-public.simplesavedobject.id.md) | | SavedObjectType<T>['id'] | | | [migrationVersion](./kibana-plugin-core-public.simplesavedobject.migrationversion.md) | | SavedObjectType<T>['migrationVersion'] | | diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 7daf5d086d9e4..4c6116540c12d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -139,7 +139,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectAttributes](./kibana-plugin-core-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | | [SavedObjectExportBaseOptions](./kibana-plugin-core-server.savedobjectexportbaseoptions.md) | | | [SavedObjectMigrationContext](./kibana-plugin-core-server.savedobjectmigrationcontext.md) | Migration context provided when invoking a [migration handler](./kibana-plugin-core-server.savedobjectmigrationfn.md) | -| [SavedObjectMigrationMap](./kibana-plugin-core-server.savedobjectmigrationmap.md) | A map of [migration functions](./kibana-plugin-core-server.savedobjectmigrationfn.md) to be used for a given type. The map's keys must be valid semver versions.For a given document, only migrations with a higher version number than that of the document will be applied. Migrations are executed in order, starting from the lowest version and ending with the highest one. | +| [SavedObjectMigrationMap](./kibana-plugin-core-server.savedobjectmigrationmap.md) | A map of [migration functions](./kibana-plugin-core-server.savedobjectmigrationfn.md) to be used for a given type. The map's keys must be valid semver versions, and they cannot exceed the current Kibana version.For a given document, only migrations with a higher version number than that of the document will be applied. Migrations are executed in order, starting from the lowest version and ending with the highest one. | | [SavedObjectReference](./kibana-plugin-core-server.savedobjectreference.md) | A reference to another saved object. | | [SavedObjectsAddToNamespacesOptions](./kibana-plugin-core-server.savedobjectsaddtonamespacesoptions.md) | | | [SavedObjectsAddToNamespacesResponse](./kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.md) | | @@ -187,10 +187,12 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsMigrationLogger](./kibana-plugin-core-server.savedobjectsmigrationlogger.md) | | | [SavedObjectsMigrationVersion](./kibana-plugin-core-server.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | | [SavedObjectsRawDoc](./kibana-plugin-core-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. | +| [SavedObjectsRawDocParseOptions](./kibana-plugin-core-server.savedobjectsrawdocparseoptions.md) | Options that can be specified when using the saved objects serializer to parse a raw document. | | [SavedObjectsRemoveReferencesToOptions](./kibana-plugin-core-server.savedobjectsremovereferencestooptions.md) | | | [SavedObjectsRemoveReferencesToResponse](./kibana-plugin-core-server.savedobjectsremovereferencestoresponse.md) | | | [SavedObjectsRepositoryFactory](./kibana-plugin-core-server.savedobjectsrepositoryfactory.md) | Factory provided when invoking a [client factory provider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) See [SavedObjectsServiceSetup.setClientFactoryProvider](./kibana-plugin-core-server.savedobjectsservicesetup.setclientfactoryprovider.md) | | [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-core-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | +| [SavedObjectsResolveResponse](./kibana-plugin-core-server.savedobjectsresolveresponse.md) | | | [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persistence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for registering Saved Object types, creating and registering Saved Object client wrappers and factories. | | [SavedObjectsServiceStart](./kibana-plugin-core-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | | [SavedObjectStatusMeta](./kibana-plugin-core-server.savedobjectstatusmeta.md) | Meta information about the SavedObjectService's status. Available to plugins via [CoreSetup.status](./kibana-plugin-core-server.coresetup.status.md). | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobject.coremigrationversion.md b/docs/development/core/server/kibana-plugin-core-server.savedobject.coremigrationversion.md new file mode 100644 index 0000000000000..b4d1f3c769451 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobject.coremigrationversion.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObject](./kibana-plugin-core-server.savedobject.md) > [coreMigrationVersion](./kibana-plugin-core-server.savedobject.coremigrationversion.md) + +## SavedObject.coreMigrationVersion property + +A semver value that is used when upgrading objects between Kibana versions. + +Signature: + +```typescript +coreMigrationVersion?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobject.md index 5aefc55736cd1..07172487e6fde 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobject.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobject.md @@ -15,6 +15,7 @@ export interface SavedObject | Property | Type | Description | | --- | --- | --- | | [attributes](./kibana-plugin-core-server.savedobject.attributes.md) | T | The data for a Saved Object is stored as an object in the attributes property. | +| [coreMigrationVersion](./kibana-plugin-core-server.savedobject.coremigrationversion.md) | string | A semver value that is used when upgrading objects between Kibana versions. | | [error](./kibana-plugin-core-server.savedobject.error.md) | SavedObjectError | | | [id](./kibana-plugin-core-server.savedobject.id.md) | string | The ID of this Saved Object, guaranteed to be unique for all objects of the same type | | [migrationVersion](./kibana-plugin-core-server.savedobject.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectmigrationmap.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectmigrationmap.md index 2ab9fcaf428b9..c07a41e28d45b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectmigrationmap.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectmigrationmap.md @@ -4,7 +4,7 @@ ## SavedObjectMigrationMap interface -A map of [migration functions](./kibana-plugin-core-server.savedobjectmigrationfn.md) to be used for a given type. The map's keys must be valid semver versions. +A map of [migration functions](./kibana-plugin-core-server.savedobjectmigrationfn.md) to be used for a given type. The map's keys must be valid semver versions, and they cannot exceed the current Kibana version. For a given document, only migrations with a higher version number than that of the document will be applied. Migrations are executed in order, starting from the lowest version and ending with the highest one. diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.coremigrationversion.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.coremigrationversion.md new file mode 100644 index 0000000000000..fb1f485cdf202 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.coremigrationversion.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsBulkCreateObject](./kibana-plugin-core-server.savedobjectsbulkcreateobject.md) > [coreMigrationVersion](./kibana-plugin-core-server.savedobjectsbulkcreateobject.coremigrationversion.md) + +## SavedObjectsBulkCreateObject.coreMigrationVersion property + +A semver value that is used when upgrading objects between Kibana versions. If undefined, this will be automatically set to the current Kibana version when the object is created. If this is set to a non-semver value, or it is set to a semver value greater than the current Kibana version, it will result in an error. + +Signature: + +```typescript +coreMigrationVersion?: string; +``` + +## Remarks + +Do not attempt to set this manually. It should only be used if you retrieved an existing object that had the `coreMigrationVersion` field set and you want to create it again. + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md index 5ac5f6d9807bd..6fc01212a2e41 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md @@ -16,6 +16,7 @@ export interface SavedObjectsBulkCreateObject | Property | Type | Description | | --- | --- | --- | | [attributes](./kibana-plugin-core-server.savedobjectsbulkcreateobject.attributes.md) | T | | +| [coreMigrationVersion](./kibana-plugin-core-server.savedobjectsbulkcreateobject.coremigrationversion.md) | string | A semver value that is used when upgrading objects between Kibana versions. If undefined, this will be automatically set to the current Kibana version when the object is created. If this is set to a non-semver value, or it is set to a semver value greater than the current Kibana version, it will result in an error. | | [id](./kibana-plugin-core-server.savedobjectsbulkcreateobject.id.md) | string | | | [initialNamespaces](./kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md) | string[] | Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).Note: this can only be used for multi-namespace object types. | | [migrationVersion](./kibana-plugin-core-server.savedobjectsbulkcreateobject.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md index 7fb34631c736e..da1f4d029ea2b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md @@ -36,5 +36,6 @@ The constructor for this class is marked as internal. Third-party code should no | [find(options)](./kibana-plugin-core-server.savedobjectsclient.find.md) | | Find all SavedObjects matching the search query | | [get(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.get.md) | | Retrieves a single object | | [removeReferencesTo(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.removereferencesto.md) | | Updates all objects containing a reference to the given {type, id} tuple to remove the said reference. | +| [resolve(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.resolve.md) | | Resolves a single object, using any legacy URL alias if it exists | | [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsclient.update.md) | | Updates an SavedObject | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.resolve.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.resolve.md new file mode 100644 index 0000000000000..b9a63f0b8c05a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.resolve.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsClient](./kibana-plugin-core-server.savedobjectsclient.md) > [resolve](./kibana-plugin-core-server.savedobjectsclient.resolve.md) + +## SavedObjectsClient.resolve() method + +Resolves a single object, using any legacy URL alias if it exists + +Signature: + +```typescript +resolve(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | The type of SavedObject to retrieve | +| id | string | The ID of the SavedObject to retrieve | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise>` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.coremigrationversion.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.coremigrationversion.md new file mode 100644 index 0000000000000..e2a4064ec4f33 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.coremigrationversion.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md) > [coreMigrationVersion](./kibana-plugin-core-server.savedobjectscreateoptions.coremigrationversion.md) + +## SavedObjectsCreateOptions.coreMigrationVersion property + +A semver value that is used when upgrading objects between Kibana versions. If undefined, this will be automatically set to the current Kibana version when the object is created. If this is set to a non-semver value, or it is set to a semver value greater than the current Kibana version, it will result in an error. + +Signature: + +```typescript +coreMigrationVersion?: string; +``` + +## Remarks + +Do not attempt to set this manually. It should only be used if you retrieved an existing object that had the `coreMigrationVersion` field set and you want to create it again. + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md index e6d306784f8ae..1805f389d4e7f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md @@ -15,6 +15,7 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions | Property | Type | Description | | --- | --- | --- | +| [coreMigrationVersion](./kibana-plugin-core-server.savedobjectscreateoptions.coremigrationversion.md) | string | A semver value that is used when upgrading objects between Kibana versions. If undefined, this will be automatically set to the current Kibana version when the object is created. If this is set to a non-semver value, or it is set to a semver value greater than the current Kibana version, it will result in an error. | | [id](./kibana-plugin-core-server.savedobjectscreateoptions.id.md) | string | (not recommended) Specify an id for the document | | [initialNamespaces](./kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md) | string[] | Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).Note: this can only be used for multi-namespace object types. | | [migrationVersion](./kibana-plugin-core-server.savedobjectscreateoptions.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrawdocparseoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrawdocparseoptions.md new file mode 100644 index 0000000000000..708d1bc9c514d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrawdocparseoptions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsRawDocParseOptions](./kibana-plugin-core-server.savedobjectsrawdocparseoptions.md) + +## SavedObjectsRawDocParseOptions interface + +Options that can be specified when using the saved objects serializer to parse a raw document. + +Signature: + +```typescript +export interface SavedObjectsRawDocParseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [namespaceTreatment](./kibana-plugin-core-server.savedobjectsrawdocparseoptions.namespacetreatment.md) | 'strict' | 'lax' | Optional setting to allow for lax handling of the raw document ID and namespace field. This is needed when a previously single-namespace object type is converted to a multi-namespace object type, and it is only intended to be used during upgrade migrations.If not specified, the default treatment is strict. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrawdocparseoptions.namespacetreatment.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrawdocparseoptions.namespacetreatment.md new file mode 100644 index 0000000000000..c315d78aaf417 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrawdocparseoptions.namespacetreatment.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsRawDocParseOptions](./kibana-plugin-core-server.savedobjectsrawdocparseoptions.md) > [namespaceTreatment](./kibana-plugin-core-server.savedobjectsrawdocparseoptions.namespacetreatment.md) + +## SavedObjectsRawDocParseOptions.namespaceTreatment property + +Optional setting to allow for lax handling of the raw document ID and namespace field. This is needed when a previously single-namespace object type is converted to a multi-namespace object type, and it is only intended to be used during upgrade migrations. + +If not specified, the default treatment is `strict`. + +Signature: + +```typescript +namespaceTreatment?: 'strict' | 'lax'; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md index c7e5b0476bad4..4d13fea12572c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md @@ -28,5 +28,6 @@ export declare class SavedObjectsRepository | [get(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.get.md) | | Gets a single object | | [incrementCounter(type, id, counterFields, options)](./kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md) | | Increments all the specified counter fields (by one by default). Creates the document if one doesn't exist for the given id. | | [removeReferencesTo(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.removereferencesto.md) | | Updates all objects containing a reference to the given {type, id} tuple to remove the said reference. | +| [resolve(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.resolve.md) | | Resolves a single object, using any legacy URL alias if it exists | | [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsrepository.update.md) | | Updates an object | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.resolve.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.resolve.md new file mode 100644 index 0000000000000..7d0a1c7d204be --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.resolve.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsRepository](./kibana-plugin-core-server.savedobjectsrepository.md) > [resolve](./kibana-plugin-core-server.savedobjectsrepository.resolve.md) + +## SavedObjectsRepository.resolve() method + +Resolves a single object, using any legacy URL alias if it exists + +Signature: + +```typescript +resolve(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise>` + +{promise} - { saved\_object, outcome } + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md new file mode 100644 index 0000000000000..cfb309da0a716 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-server.savedobjectsresolveresponse.md) + +## SavedObjectsResolveResponse interface + + +Signature: + +```typescript +export interface SavedObjectsResolveResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [outcome](./kibana-plugin-core-server.savedobjectsresolveresponse.outcome.md) | 'exactMatch' | 'aliasMatch' | 'conflict' | The outcome for a successful resolve call is one of the following values:\* 'exactMatch' -- One document exactly matched the given ID. \* 'aliasMatch' -- One document with a legacy URL alias matched the given ID; in this case the saved_object.id field is different than the given ID. \* 'conflict' -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the saved_object object is the exact match, and the saved_object.id field is the same as the given ID. | +| [saved\_object](./kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md) | SavedObject<T> | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.outcome.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.outcome.md new file mode 100644 index 0000000000000..eadd85b175375 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.outcome.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-server.savedobjectsresolveresponse.md) > [outcome](./kibana-plugin-core-server.savedobjectsresolveresponse.outcome.md) + +## SavedObjectsResolveResponse.outcome property + +The outcome for a successful `resolve` call is one of the following values: + +\* `'exactMatch'` -- One document exactly matched the given ID. \* `'aliasMatch'` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different than the given ID. \* `'conflict'` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID. + +Signature: + +```typescript +outcome: 'exactMatch' | 'aliasMatch' | 'conflict'; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md new file mode 100644 index 0000000000000..c184312675f75 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-server.savedobjectsresolveresponse.md) > [saved\_object](./kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md) + +## SavedObjectsResolveResponse.saved\_object property + +Signature: + +```typescript +saved_object: SavedObject; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.generaterawlegacyurlaliasid.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.generaterawlegacyurlaliasid.md new file mode 100644 index 0000000000000..d33f42ee2cf5f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.generaterawlegacyurlaliasid.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsSerializer](./kibana-plugin-core-server.savedobjectsserializer.md) > [generateRawLegacyUrlAliasId](./kibana-plugin-core-server.savedobjectsserializer.generaterawlegacyurlaliasid.md) + +## SavedObjectsSerializer.generateRawLegacyUrlAliasId() method + +Given a saved object type and id, generates the compound id that is stored in the raw document for its legacy URL alias. + +Signature: + +```typescript +generateRawLegacyUrlAliasId(namespace: string, type: string, id: string): string; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| namespace | string | | +| type | string | | +| id | string | | + +Returns: + +`string` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.israwsavedobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.israwsavedobject.md index b9033b00624cc..1094cc25ab557 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.israwsavedobject.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.israwsavedobject.md @@ -9,14 +9,15 @@ Determines whether or not the raw document can be converted to a saved object. Signature: ```typescript -isRawSavedObject(rawDoc: SavedObjectsRawDoc): boolean; +isRawSavedObject(doc: SavedObjectsRawDoc, options?: SavedObjectsRawDocParseOptions): boolean; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| rawDoc | SavedObjectsRawDoc | | +| doc | SavedObjectsRawDoc | | +| options | SavedObjectsRawDocParseOptions | | Returns: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.md index 129e6d8bf90f8..c7fa5fc85c613 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.md @@ -23,7 +23,8 @@ The constructor for this class is marked as internal. Third-party code should no | Method | Modifiers | Description | | --- | --- | --- | | [generateRawId(namespace, type, id)](./kibana-plugin-core-server.savedobjectsserializer.generaterawid.md) | | Given a saved object type and id, generates the compound id that is stored in the raw document. | -| [isRawSavedObject(rawDoc)](./kibana-plugin-core-server.savedobjectsserializer.israwsavedobject.md) | | Determines whether or not the raw document can be converted to a saved object. | -| [rawToSavedObject(doc)](./kibana-plugin-core-server.savedobjectsserializer.rawtosavedobject.md) | | Converts a document from the format that is stored in elasticsearch to the saved object client format. | +| [generateRawLegacyUrlAliasId(namespace, type, id)](./kibana-plugin-core-server.savedobjectsserializer.generaterawlegacyurlaliasid.md) | | Given a saved object type and id, generates the compound id that is stored in the raw document for its legacy URL alias. | +| [isRawSavedObject(doc, options)](./kibana-plugin-core-server.savedobjectsserializer.israwsavedobject.md) | | Determines whether or not the raw document can be converted to a saved object. | +| [rawToSavedObject(doc, options)](./kibana-plugin-core-server.savedobjectsserializer.rawtosavedobject.md) | | Converts a document from the format that is stored in elasticsearch to the saved object client format. | | [savedObjectToRaw(savedObj)](./kibana-plugin-core-server.savedobjectsserializer.savedobjecttoraw.md) | | Converts a document from the saved object client format to the format that is stored in elasticsearch. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.rawtosavedobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.rawtosavedobject.md index dc9a2ef85839f..3fc386f263141 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.rawtosavedobject.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsserializer.rawtosavedobject.md @@ -9,7 +9,7 @@ Converts a document from the format that is stored in elasticsearch to the saved Signature: ```typescript -rawToSavedObject(doc: SavedObjectsRawDoc): SavedObjectSanitizedDoc; +rawToSavedObject(doc: SavedObjectsRawDoc, options?: SavedObjectsRawDocParseOptions): SavedObjectSanitizedDoc; ``` ## Parameters @@ -17,6 +17,7 @@ rawToSavedObject(doc: SavedObjectsRawDoc): SavedObjectSanitizedDoc; | Parameter | Type | Description | | --- | --- | --- | | doc | SavedObjectsRawDoc | | +| options | SavedObjectsRawDocParseOptions | | Returns: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.converttomultinamespacetypeversion.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.converttomultinamespacetypeversion.md new file mode 100644 index 0000000000000..064bd0b35699d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.converttomultinamespacetypeversion.md @@ -0,0 +1,42 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsType](./kibana-plugin-core-server.savedobjectstype.md) > [convertToMultiNamespaceTypeVersion](./kibana-plugin-core-server.savedobjectstype.converttomultinamespacetypeversion.md) + +## SavedObjectsType.convertToMultiNamespaceTypeVersion property + +If defined, objects of this type will be converted to multi-namespace objects when migrating to this version. + +Requirements: + +1. This string value must be a valid semver version 2. This type must have previously specified [\`namespaceType: 'single'\`](./kibana-plugin-core-server.savedobjectsnamespacetype.md) 3. This type must also specify [\`namespaceType: 'multiple'\`](./kibana-plugin-core-server.savedobjectsnamespacetype.md) + +Example of a single-namespace type in 7.10: + +```ts +{ + name: 'foo', + hidden: false, + namespaceType: 'single', + mappings: {...} +} + +``` +Example after converting to a multi-namespace type in 7.11: + +```ts +{ + name: 'foo', + hidden: false, + namespaceType: 'multiple', + mappings: {...}, + convertToMultiNamespaceTypeVersion: '7.11.0' +} + +``` +Note: a migration function can be optionally specified for the same version. + +Signature: + +```typescript +convertToMultiNamespaceTypeVersion?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.md index e5c3fa2b3e92d..eacad53be39fe 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstype.md @@ -19,6 +19,28 @@ This is only internal for now, and will only be public when we expose the regist | Property | Type | Description | | --- | --- | --- | | [convertToAliasScript](./kibana-plugin-core-server.savedobjectstype.converttoaliasscript.md) | string | If defined, will be used to convert the type to an alias. | +| [convertToMultiNamespaceTypeVersion](./kibana-plugin-core-server.savedobjectstype.converttomultinamespacetypeversion.md) | string | If defined, objects of this type will be converted to multi-namespace objects when migrating to this version.Requirements:1. This string value must be a valid semver version 2. This type must have previously specified [\`namespaceType: 'single'\`](./kibana-plugin-core-server.savedobjectsnamespacetype.md) 3. This type must also specify [\`namespaceType: 'multiple'\`](./kibana-plugin-core-server.savedobjectsnamespacetype.md)Example of a single-namespace type in 7.10: +```ts +{ + name: 'foo', + hidden: false, + namespaceType: 'single', + mappings: {...} +} + +``` +Example after converting to a multi-namespace type in 7.11: +```ts +{ + name: 'foo', + hidden: false, + namespaceType: 'multiple', + mappings: {...}, + convertToMultiNamespaceTypeVersion: '7.11.0' +} + +``` +Note: a migration function can be optionally specified for the same version. | | [hidden](./kibana-plugin-core-server.savedobjectstype.hidden.md) | boolean | Is the type hidden by default. If true, repositories will not have access to this type unless explicitly declared as an extraType when creating the repository.See [createInternalRepository](./kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md). | | [indexPattern](./kibana-plugin-core-server.savedobjectstype.indexpattern.md) | string | If defined, the type instances will be stored in the given index instead of the default one. | | [management](./kibana-plugin-core-server.savedobjectstype.management.md) | SavedObjectsTypeManagementDefinition | An optional [saved objects management section](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) definition for the type. | 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 88f85eb7a7d05..8f1ea7b95a5f9 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 @@ -12,7 +12,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -31,7 +31,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/docs/user/security/audit-logging.asciidoc b/docs/user/security/audit-logging.asciidoc index acb0f94cf878c..12a87b1422c5c 100644 --- a/docs/user/security/audit-logging.asciidoc +++ b/docs/user/security/audit-logging.asciidoc @@ -194,6 +194,10 @@ Refer to the corresponding {es} logs for potential write errors. | `success` | User has accessed a saved object. | `failure` | User is not authorized to access a saved object. +.2+| `saved_object_resolve` +| `success` | User has accessed a saved object. +| `failure` | User is not authorized to access a saved object. + .2+| `saved_object_find` | `success` | User has accessed a saved object as part of a search operation. | `failure` | User is not authorized to search for saved objects. diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index da818470133cd..0a166d4511c5f 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1027,6 +1027,7 @@ export type PublicUiSettingsParams = Omit; // @public (undocumented) export interface SavedObject { attributes: T; + coreMigrationVersion?: string; // (undocumented) error?: SavedObjectError; id: string; @@ -1144,6 +1145,7 @@ export type SavedObjectsClientContract = PublicMethodsOf; // @public (undocumented) export interface SavedObjectsCreateOptions { + coreMigrationVersion?: string; id?: string; migrationVersion?: SavedObjectsMigrationVersion; overwrite?: boolean; @@ -1377,10 +1379,12 @@ export class ScopedHistory implements History { - constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion }: SavedObject); + constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, }: SavedObject); // (undocumented) attributes: T; // (undocumented) + coreMigrationVersion: SavedObject['coreMigrationVersion']; + // (undocumented) delete(): Promise<{}>; // (undocumented) error: SavedObject['error']; diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index 6c24cf2d0971b..fdef63c392db6 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -38,6 +38,8 @@ export interface SavedObjectsCreateOptions { overwrite?: boolean; /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; + /** A semver value that is used when upgrading objects between Kibana versions. */ + coreMigrationVersion?: string; references?: SavedObjectReference[]; } diff --git a/src/core/public/saved_objects/simple_saved_object.ts b/src/core/public/saved_objects/simple_saved_object.ts index a0ebc8214aaec..0eb0e0b53f78e 100644 --- a/src/core/public/saved_objects/simple_saved_object.ts +++ b/src/core/public/saved_objects/simple_saved_object.ts @@ -27,12 +27,22 @@ export class SimpleSavedObject { public id: SavedObjectType['id']; public type: SavedObjectType['type']; public migrationVersion: SavedObjectType['migrationVersion']; + public coreMigrationVersion: SavedObjectType['coreMigrationVersion']; public error: SavedObjectType['error']; public references: SavedObjectType['references']; constructor( private client: SavedObjectsClientContract, - { id, type, version, attributes, error, references, migrationVersion }: SavedObjectType + { + id, + type, + version, + attributes, + error, + references, + migrationVersion, + coreMigrationVersion, + }: SavedObjectType ) { this.id = id; this.type = type; @@ -40,6 +50,7 @@ export class SimpleSavedObject { this.references = references || []; this._version = version; this.migrationVersion = migrationVersion; + this.coreMigrationVersion = coreMigrationVersion; if (error) { this.error = error; } @@ -66,6 +77,7 @@ export class SimpleSavedObject { } else { return this.client.create(this.type, this.attributes, { migrationVersion: this.migrationVersion, + coreMigrationVersion: this.coreMigrationVersion, references: this.references, }); } diff --git a/src/core/server/core_usage_data/core_usage_stats_client.mock.ts b/src/core/server/core_usage_data/core_usage_stats_client.mock.ts index 8495f2e0d082a..8a0aaa646438d 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.mock.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.mock.ts @@ -18,6 +18,7 @@ const createUsageStatsClientMock = () => incrementSavedObjectsDelete: jest.fn().mockResolvedValue(null), incrementSavedObjectsFind: jest.fn().mockResolvedValue(null), incrementSavedObjectsGet: jest.fn().mockResolvedValue(null), + incrementSavedObjectsResolve: jest.fn().mockResolvedValue(null), incrementSavedObjectsUpdate: jest.fn().mockResolvedValue(null), incrementSavedObjectsImport: jest.fn().mockResolvedValue(null), incrementSavedObjectsResolveImportErrors: jest.fn().mockResolvedValue(null), diff --git a/src/core/server/core_usage_data/core_usage_stats_client.test.ts b/src/core/server/core_usage_data/core_usage_stats_client.test.ts index 0e43363dddb77..2067466c63510 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.test.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.test.ts @@ -20,6 +20,7 @@ import { DELETE_STATS_PREFIX, FIND_STATS_PREFIX, GET_STATS_PREFIX, + RESOLVE_STATS_PREFIX, UPDATE_STATS_PREFIX, IMPORT_STATS_PREFIX, RESOLVE_IMPORT_STATS_PREFIX, @@ -594,6 +595,81 @@ describe('CoreUsageStatsClient', () => { }); }); + describe('#incrementSavedObjectsResolve', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + const request = httpServerMock.createKibanaRequest(); + await expect( + usageStatsClient.incrementSavedObjectsResolve({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsResolve({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${RESOLVE_STATS_PREFIX}.total`, + `${RESOLVE_STATS_PREFIX}.namespace.default.total`, + `${RESOLVE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsResolve({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${RESOLVE_STATS_PREFIX}.total`, + `${RESOLVE_STATS_PREFIX}.namespace.default.total`, + `${RESOLVE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsResolve({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${RESOLVE_STATS_PREFIX}.total`, + `${RESOLVE_STATS_PREFIX}.namespace.custom.total`, + `${RESOLVE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + describe('#incrementSavedObjectsUpdate', () => { it('does not throw an error if repository incrementCounter operation fails', async () => { const { usageStatsClient, repositoryMock } = setup(); diff --git a/src/core/server/core_usage_data/core_usage_stats_client.ts b/src/core/server/core_usage_data/core_usage_stats_client.ts index 103e98d2ef37e..70bdb99f666fd 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.ts @@ -40,6 +40,7 @@ export const CREATE_STATS_PREFIX = 'apiCalls.savedObjectsCreate'; export const DELETE_STATS_PREFIX = 'apiCalls.savedObjectsDelete'; export const FIND_STATS_PREFIX = 'apiCalls.savedObjectsFind'; export const GET_STATS_PREFIX = 'apiCalls.savedObjectsGet'; +export const RESOLVE_STATS_PREFIX = 'apiCalls.savedObjectsResolve'; export const UPDATE_STATS_PREFIX = 'apiCalls.savedObjectsUpdate'; export const IMPORT_STATS_PREFIX = 'apiCalls.savedObjectsImport'; export const RESOLVE_IMPORT_STATS_PREFIX = 'apiCalls.savedObjectsResolveImportErrors'; @@ -53,6 +54,7 @@ const ALL_COUNTER_FIELDS = [ ...getFieldsForCounter(DELETE_STATS_PREFIX), ...getFieldsForCounter(FIND_STATS_PREFIX), ...getFieldsForCounter(GET_STATS_PREFIX), + ...getFieldsForCounter(RESOLVE_STATS_PREFIX), ...getFieldsForCounter(UPDATE_STATS_PREFIX), // Saved Objects Management APIs ...getFieldsForCounter(IMPORT_STATS_PREFIX), @@ -123,6 +125,10 @@ export class CoreUsageStatsClient { await this.updateUsageStats([], GET_STATS_PREFIX, options); } + public async incrementSavedObjectsResolve(options: BaseIncrementOptions) { + await this.updateUsageStats([], RESOLVE_STATS_PREFIX, options); + } + public async incrementSavedObjectsUpdate(options: BaseIncrementOptions) { await this.updateUsageStats([], UPDATE_STATS_PREFIX, options); } diff --git a/src/core/server/core_usage_data/types.ts b/src/core/server/core_usage_data/types.ts index bd79e118c4460..505dd8528e755 100644 --- a/src/core/server/core_usage_data/types.ts +++ b/src/core/server/core_usage_data/types.ts @@ -66,6 +66,13 @@ export interface CoreUsageStats { 'apiCalls.savedObjectsGet.namespace.custom.total'?: number; 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes'?: number; 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsResolve.total'?: number; + 'apiCalls.savedObjectsResolve.namespace.default.total'?: number; + 'apiCalls.savedObjectsResolve.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsResolve.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsResolve.namespace.custom.total'?: number; + 'apiCalls.savedObjectsResolve.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsResolve.namespace.custom.kibanaRequest.no'?: number; 'apiCalls.savedObjectsUpdate.total'?: number; 'apiCalls.savedObjectsUpdate.namespace.default.total'?: number; 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes'?: number; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 0eb246b4c978b..a27863a458f2b 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -277,10 +277,12 @@ export { SavedObjectMigrationContext, SavedObjectsMigrationLogger, SavedObjectsRawDoc, + SavedObjectsRawDocParseOptions, SavedObjectSanitizedDoc, SavedObjectUnsanitizedDoc, SavedObjectsRepositoryFactory, SavedObjectsResolveImportErrorsOptions, + SavedObjectsResolveResponse, SavedObjectsSerializer, SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 57dee5cd51f1d..86ee7de5fab54 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -43,6 +43,7 @@ export { export { SavedObjectsSerializer, SavedObjectsRawDoc, + SavedObjectsRawDocParseOptions, SavedObjectSanitizedDoc, SavedObjectUnsanitizedDoc, } from './serialization'; diff --git a/src/core/server/saved_objects/migrations/core/__mocks__/index.ts b/src/core/server/saved_objects/migrations/core/__mocks__/index.ts new file mode 100644 index 0000000000000..b22ad0c93b234 --- /dev/null +++ b/src/core/server/saved_objects/migrations/core/__mocks__/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const mockUuidv5 = jest.fn().mockReturnValue('uuidv5'); +Object.defineProperty(mockUuidv5, 'DNS', { value: 'DNSUUID', writable: false }); +jest.mock('uuid/v5', () => mockUuidv5); + +export { mockUuidv5 }; diff --git a/src/core/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap b/src/core/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap index f8ef47cae8944..9ee998118bde6 100644 --- a/src/core/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap +++ b/src/core/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap @@ -6,6 +6,7 @@ Object { "migrationMappingPropertyHashes": Object { "aaa": "625b32086eb1d1203564cf85062dd22e", "bbb": "18c78c995965207ed3f6e7fc5c6e55fe", + "coreMigrationVersion": "2f4316de49999235636386fe51dc06c1", "migrationVersion": "4a1746014a75ade3a714e1db5763276f", "namespace": "2f4316de49999235636386fe51dc06c1", "namespaces": "2f4316de49999235636386fe51dc06c1", @@ -23,6 +24,9 @@ Object { "bbb": Object { "type": "long", }, + "coreMigrationVersion": Object { + "type": "keyword", + }, "migrationVersion": Object { "dynamic": "true", "type": "object", @@ -64,6 +68,7 @@ exports[`buildActiveMappings handles the \`dynamic\` property of types 1`] = ` Object { "_meta": Object { "migrationMappingPropertyHashes": Object { + "coreMigrationVersion": "2f4316de49999235636386fe51dc06c1", "firstType": "635418ab953d81d93f1190b70a8d3f57", "migrationVersion": "4a1746014a75ade3a714e1db5763276f", "namespace": "2f4316de49999235636386fe51dc06c1", @@ -78,6 +83,9 @@ Object { }, "dynamic": "strict", "properties": Object { + "coreMigrationVersion": Object { + "type": "keyword", + }, "firstType": Object { "dynamic": "strict", "properties": Object { diff --git a/src/core/server/saved_objects/migrations/core/build_active_mappings.ts b/src/core/server/saved_objects/migrations/core/build_active_mappings.ts index 594c6e4e3df6a..83e7b1549bc97 100644 --- a/src/core/server/saved_objects/migrations/core/build_active_mappings.ts +++ b/src/core/server/saved_objects/migrations/core/build_active_mappings.ts @@ -153,6 +153,9 @@ function defaultMapping(): IndexMapping { }, }, }, + coreMigrationVersion: { + type: 'keyword', + }, }, }; } diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts index 9b97867bf187f..741f715ba6ebe 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts @@ -6,6 +6,7 @@ * Public License, v 1. */ +import { mockUuidv5 } from './__mocks__'; import { set } from '@elastic/safer-lodash-set'; import _ from 'lodash'; import { SavedObjectUnsanitizedDoc } from '../../serialization'; @@ -13,9 +14,11 @@ import { DocumentMigrator } from './document_migrator'; import { loggingSystemMock } from '../../../logging/logging_system.mock'; import { SavedObjectsType } from '../../types'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; +import { LEGACY_URL_ALIAS_TYPE } from '../../object_types'; const mockLoggerFactory = loggingSystemMock.create(); const mockLogger = mockLoggerFactory.get('mock logger'); +const kibanaVersion = '25.2.3'; const createRegistry = (...types: Array>) => { const registry = new SavedObjectTypeRegistry(); @@ -32,644 +35,1216 @@ const createRegistry = (...types: Array>) => { return registry; }; +beforeEach(() => { + mockUuidv5.mockClear(); +}); + describe('DocumentMigrator', () => { function testOpts() { return { - kibanaVersion: '25.2.3', + kibanaVersion, typeRegistry: createRegistry(), + minimumConvertVersion: '0.0.0', // no minimum version unless we specify it for a test case log: mockLogger, }; } - const createDefinition = (migrations: any) => ({ - kibanaVersion: '3.2.3', - typeRegistry: createRegistry({ - name: 'foo', - migrations: migrations as any, - }), - log: mockLogger, - }); + describe('validation', () => { + const createDefinition = (migrations: any) => ({ + kibanaVersion: '3.2.3', + typeRegistry: createRegistry({ + name: 'foo', + migrations: migrations as any, + }), + log: mockLogger, + }); - it('validates migration definition', () => { - expect(() => new DocumentMigrator(createDefinition(() => {}))).not.toThrow(); - expect(() => new DocumentMigrator(createDefinition({}))).not.toThrow(); - expect(() => new DocumentMigrator(createDefinition(123))).toThrow( - /Migration for type foo should be an object or a function/i - ); - }); + describe('#prepareMigrations', () => { + it('validates individual migration definitions', () => { + const invalidMigrator = new DocumentMigrator(createDefinition(() => 123)); + const voidMigrator = new DocumentMigrator(createDefinition(() => {})); + const emptyObjectMigrator = new DocumentMigrator(createDefinition(() => ({}))); - describe('#prepareMigrations', () => { - it('validates individual migration definitions', () => { - const invalidMigrator = new DocumentMigrator(createDefinition(() => 123)); - const voidMigrator = new DocumentMigrator(createDefinition(() => {})); - const emptyObjectMigrator = new DocumentMigrator(createDefinition(() => ({}))); + expect(invalidMigrator.prepareMigrations).toThrow( + /Migrations map for type foo should be an object/i + ); + expect(voidMigrator.prepareMigrations).not.toThrow(); + expect(emptyObjectMigrator.prepareMigrations).not.toThrow(); + }); - expect(invalidMigrator.prepareMigrations).toThrow( - /Migrations map for type foo should be an object/i - ); - expect(voidMigrator.prepareMigrations).not.toThrow(); - expect(emptyObjectMigrator.prepareMigrations).not.toThrow(); - }); + it('validates individual migrations are valid semvers', () => { + const withInvalidVersion = { + bar: (doc: any) => doc, + '1.2.3': (doc: any) => doc, + }; + const migrationFn = new DocumentMigrator(createDefinition(() => withInvalidVersion)); + const migrationObj = new DocumentMigrator(createDefinition(withInvalidVersion)); - it('validates individual migration semvers', () => { - const withInvalidVersion = { - bar: (doc: any) => doc, - '1.2.3': (doc: any) => doc, - }; - const migrationFn = new DocumentMigrator(createDefinition(() => withInvalidVersion)); - const migrationObj = new DocumentMigrator(createDefinition(withInvalidVersion)); + expect(migrationFn.prepareMigrations).toThrow(/Expected all properties to be semvers/i); + expect(migrationObj.prepareMigrations).toThrow(/Expected all properties to be semvers/i); + }); - expect(migrationFn.prepareMigrations).toThrow(/Expected all properties to be semvers/i); - expect(migrationObj.prepareMigrations).toThrow(/Expected all properties to be semvers/i); - }); + it('validates individual migrations are not greater than the current Kibana version', () => { + const withGreaterVersion = { + '3.2.4': (doc: any) => doc, + }; + const migrationFn = new DocumentMigrator(createDefinition(() => withGreaterVersion)); + const migrationObj = new DocumentMigrator(createDefinition(withGreaterVersion)); - it('validates the migration function', () => { - const invalidVersionFunction = { '1.2.3': 23 as any }; - const migrationFn = new DocumentMigrator(createDefinition(() => invalidVersionFunction)); - const migrationObj = new DocumentMigrator(createDefinition(invalidVersionFunction)); + const expectedError = `Invalid migration for type foo. Property '3.2.4' cannot be greater than the current Kibana version '3.2.3'.`; + expect(migrationFn.prepareMigrations).toThrowError(expectedError); + expect(migrationObj.prepareMigrations).toThrowError(expectedError); + }); - expect(migrationFn.prepareMigrations).toThrow(/expected a function, but got 23/i); - expect(migrationObj.prepareMigrations).toThrow(/expected a function, but got 23/i); - }); - it('validates definitions with migrations: Function | Objects', () => { - const validMigrationMap = { '1.2.3': () => {} }; - const migrationFn = new DocumentMigrator(createDefinition(() => validMigrationMap)); - const migrationObj = new DocumentMigrator(createDefinition(validMigrationMap)); - expect(migrationFn.prepareMigrations).not.toThrow(); - expect(migrationObj.prepareMigrations).not.toThrow(); - }); - }); + it('validates the migration function', () => { + const invalidVersionFunction = { '1.2.3': 23 as any }; + const migrationFn = new DocumentMigrator(createDefinition(() => invalidVersionFunction)); + const migrationObj = new DocumentMigrator(createDefinition(invalidVersionFunction)); - it('throws if #prepareMigrations is not called before #migrate is called', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'user', - migrations: { - '1.2.3': setAttr('attributes.name', 'Chris'), - }, - }), + expect(migrationFn.prepareMigrations).toThrow(/expected a function, but got 23/i); + expect(migrationObj.prepareMigrations).toThrow(/expected a function, but got 23/i); + }); + it('validates definitions with migrations: Function | Objects', () => { + const validMigrationMap = { '1.2.3': () => {} }; + const migrationFn = new DocumentMigrator(createDefinition(() => validMigrationMap)); + const migrationObj = new DocumentMigrator(createDefinition(validMigrationMap)); + expect(migrationFn.prepareMigrations).not.toThrow(); + expect(migrationObj.prepareMigrations).not.toThrow(); + }); }); - expect(() => - migrator.migrate({ - id: 'me', - type: 'user', - attributes: { name: 'Christopher' }, - migrationVersion: {}, - }) - ).toThrow(/Migrations are not ready. Make sure prepareMigrations is called first./i); - }); + it('throws if #prepareMigrations is not called before #migrate or #migrateAndConvert is called', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'user', + migrations: { + '1.2.3': setAttr('attributes.name', 'Chris'), + }, + }), + }); - it('migrates type and attributes', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'user', - migrations: { - '1.2.3': setAttr('attributes.name', 'Chris'), - }, - }), + expect(() => + migrator.migrate({ + id: 'me', + type: 'user', + attributes: { name: 'Christopher' }, + migrationVersion: {}, + }) + ).toThrow(/Migrations are not ready. Make sure prepareMigrations is called first./i); + + expect(() => + migrator.migrateAndConvert({ + id: 'me', + type: 'user', + attributes: { name: 'Christopher' }, + migrationVersion: {}, + }) + ).toThrow(/Migrations are not ready. Make sure prepareMigrations is called first./i); }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'me', - type: 'user', - attributes: { name: 'Christopher' }, - migrationVersion: {}, + it(`validates convertToMultiNamespaceTypeVersion can only be used with namespaceType 'multiple'`, () => { + const invalidDefinition = { + kibanaVersion: '3.2.3', + typeRegistry: createRegistry({ + name: 'foo', + convertToMultiNamespaceTypeVersion: 'bar', + }), + minimumConvertVersion: '0.0.0', + log: mockLogger, + }; + expect(() => new DocumentMigrator(invalidDefinition)).toThrow( + `Invalid convertToMultiNamespaceTypeVersion for type foo. Expected namespaceType to be 'multiple', but got 'single'.` + ); }); - expect(actual).toEqual({ - id: 'me', - type: 'user', - attributes: { name: 'Chris' }, - migrationVersion: { user: '1.2.3' }, + + it(`validates convertToMultiNamespaceTypeVersion must be a semver`, () => { + const invalidDefinition = { + kibanaVersion: '3.2.3', + typeRegistry: createRegistry({ + name: 'foo', + convertToMultiNamespaceTypeVersion: 'bar', + namespaceType: 'multiple', + }), + minimumConvertVersion: '0.0.0', + log: mockLogger, + }; + expect(() => new DocumentMigrator(invalidDefinition)).toThrow( + `Invalid convertToMultiNamespaceTypeVersion for type foo. Expected value to be a semver, but got 'bar'.` + ); }); - }); - it(`doesn't mutate the original document`, () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'user', - migrations: { - '1.2.3': (doc) => { - set(doc, 'attributes.name', 'Mike'); - return doc; - }, - }, - }), + it('validates convertToMultiNamespaceTypeVersion is not less than the minimum allowed version', () => { + const invalidDefinition = { + kibanaVersion: '3.2.3', + typeRegistry: createRegistry({ + name: 'foo', + convertToMultiNamespaceTypeVersion: '3.2.4', + namespaceType: 'multiple', + }), + // not using a minimumConvertVersion parameter, the default is 8.0.0 + log: mockLogger, + }; + expect(() => new DocumentMigrator(invalidDefinition)).toThrowError( + `Invalid convertToMultiNamespaceTypeVersion for type foo. Value '3.2.4' cannot be less than '8.0.0'.` + ); }); - const originalDoc = { - id: 'me', - type: 'user', - attributes: {}, - migrationVersion: {}, - }; - migrator.prepareMigrations(); - const migratedDoc = migrator.migrate(originalDoc); - expect(_.get(originalDoc, 'attributes.name')).toBeUndefined(); - expect(_.get(migratedDoc, 'attributes.name')).toBe('Mike'); - }); - it('migrates root properties', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'acl', - migrations: { - '2.3.5': setAttr('acl', 'admins-only, sucka!'), - }, - }), + it('validates convertToMultiNamespaceTypeVersion is not greater than the current Kibana version', () => { + const invalidDefinition = { + kibanaVersion: '3.2.3', + typeRegistry: createRegistry({ + name: 'foo', + convertToMultiNamespaceTypeVersion: '3.2.4', + namespaceType: 'multiple', + }), + minimumConvertVersion: '0.0.0', + log: mockLogger, + }; + expect(() => new DocumentMigrator(invalidDefinition)).toThrowError( + `Invalid convertToMultiNamespaceTypeVersion for type foo. Value '3.2.4' cannot be greater than the current Kibana version '3.2.3'.` + ); }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'me', - type: 'user', - attributes: { name: 'Tyler' }, - acl: 'anyone', - migrationVersion: {}, - } as SavedObjectUnsanitizedDoc); - expect(actual).toEqual({ - id: 'me', - type: 'user', - attributes: { name: 'Tyler' }, - migrationVersion: { acl: '2.3.5' }, - acl: 'admins-only, sucka!', + + it('validates convertToMultiNamespaceTypeVersion is not used on a patch version', () => { + const invalidDefinition = { + kibanaVersion: '3.2.3', + typeRegistry: createRegistry({ + name: 'foo', + convertToMultiNamespaceTypeVersion: '3.1.1', + namespaceType: 'multiple', + }), + minimumConvertVersion: '0.0.0', + log: mockLogger, + }; + expect(() => new DocumentMigrator(invalidDefinition)).toThrowError( + `Invalid convertToMultiNamespaceTypeVersion for type foo. Value '3.1.1' cannot be used on a patch version (must be like 'x.y.0').` + ); }); }); - it('does not apply migrations to unrelated docs', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry( - { - name: 'aaa', - migrations: { - '1.0.0': setAttr('aaa', 'A'), - }, - }, - { - name: 'bbb', - migrations: { - '1.0.0': setAttr('bbb', 'B'), - }, - }, - { - name: 'ccc', + describe('migration', () => { + it('migrates type and attributes', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'user', migrations: { - '1.0.0': setAttr('ccc', 'C'), + '1.2.3': setAttr('attributes.name', 'Chris'), }, - } - ), - }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'me', - type: 'user', - attributes: { name: 'Tyler' }, - migrationVersion: {}, - }); - expect(actual).toEqual({ - id: 'me', - type: 'user', - attributes: { name: 'Tyler' }, + }), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ + id: 'me', + type: 'user', + attributes: { name: 'Christopher' }, + migrationVersion: {}, + }); + expect(actual).toEqual({ + id: 'me', + type: 'user', + attributes: { name: 'Chris' }, + migrationVersion: { user: '1.2.3' }, + coreMigrationVersion: kibanaVersion, + }); }); - }); - it('assumes documents w/ undefined migrationVersion are up to date', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry( - { + it(`doesn't mutate the original document`, () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ name: 'user', migrations: { - '1.0.0': setAttr('aaa', 'A'), - }, - }, - { - name: 'bbb', - migrations: { - '2.3.4': setAttr('bbb', 'B'), + '1.2.3': (doc) => { + set(doc, 'attributes.name', 'Mike'); + return doc; + }, }, - }, - { - name: 'ccc', + }), + }); + migrator.prepareMigrations(); + const originalDoc = { + id: 'me', + type: 'user', + attributes: {}, + migrationVersion: {}, + }; + const migratedDoc = migrator.migrate(originalDoc); + expect(_.get(originalDoc, 'attributes.name')).toBeUndefined(); + expect(_.get(migratedDoc, 'attributes.name')).toBe('Mike'); + }); + + it('migrates root properties', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'acl', migrations: { - '1.0.0': setAttr('ccc', 'C'), + '2.3.5': setAttr('acl', 'admins-only, sucka!'), }, - } - ), + }), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ + id: 'me', + type: 'user', + attributes: { name: 'Tyler' }, + acl: 'anyone', + migrationVersion: {}, + } as SavedObjectUnsanitizedDoc); + expect(actual).toEqual({ + id: 'me', + type: 'user', + attributes: { name: 'Tyler' }, + migrationVersion: { acl: '2.3.5' }, + acl: 'admins-only, sucka!', + coreMigrationVersion: kibanaVersion, + }); }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'me', - type: 'user', - attributes: { name: 'Tyler' }, - bbb: 'Shazm', - } as SavedObjectUnsanitizedDoc); - expect(actual).toEqual({ - id: 'me', - type: 'user', - attributes: { name: 'Tyler' }, - bbb: 'Shazm', - migrationVersion: { - user: '1.0.0', - bbb: '2.3.4', - }, + + it('does not apply migrations to unrelated docs', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { + name: 'aaa', + migrations: { + '1.0.0': setAttr('aaa', 'A'), + }, + }, + { + name: 'bbb', + migrations: { + '1.0.0': setAttr('bbb', 'B'), + }, + }, + { + name: 'ccc', + migrations: { + '1.0.0': setAttr('ccc', 'C'), + }, + } + ), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ + id: 'me', + type: 'user', + attributes: { name: 'Tyler' }, + migrationVersion: {}, + }); + expect(actual).toEqual({ + id: 'me', + type: 'user', + attributes: { name: 'Tyler' }, + coreMigrationVersion: kibanaVersion, + }); }); - }); - it('only applies migrations that are more recent than the doc', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'dog', - migrations: { - '1.2.3': setAttr('attributes.a', 'A'), - '1.2.4': setAttr('attributes.b', 'B'), - '2.0.1': setAttr('attributes.c', 'C'), + it('assumes documents w/ undefined migrationVersion and correct coreMigrationVersion are up to date', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { + name: 'user', + migrations: { + '1.0.0': setAttr('aaa', 'A'), + }, + }, + { + name: 'bbb', + migrations: { + '2.3.4': setAttr('bbb', 'B'), + }, + }, + { + name: 'ccc', + migrations: { + '1.0.0': setAttr('ccc', 'C'), + }, + } + ), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ + id: 'me', + type: 'user', + attributes: { name: 'Tyler' }, + bbb: 'Shazm', + coreMigrationVersion: kibanaVersion, + } as SavedObjectUnsanitizedDoc); + expect(actual).toEqual({ + id: 'me', + type: 'user', + attributes: { name: 'Tyler' }, + bbb: 'Shazm', + migrationVersion: { + user: '1.0.0', + bbb: '2.3.4', }, - }), - }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'smelly', - type: 'dog', - attributes: { name: 'Callie' }, - migrationVersion: { dog: '1.2.3' }, - }); - expect(actual).toEqual({ - id: 'smelly', - type: 'dog', - attributes: { name: 'Callie', b: 'B', c: 'C' }, - migrationVersion: { dog: '2.0.1' }, + coreMigrationVersion: kibanaVersion, + }); }); - }); - it('rejects docs that belong to a newer Kibana instance', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - kibanaVersion: '8.0.1', - }); - migrator.prepareMigrations(); - expect(() => - migrator.migrate({ + it('only applies migrations that are more recent than the doc', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'dog', + migrations: { + '1.2.3': setAttr('attributes.a', 'A'), + '1.2.4': setAttr('attributes.b', 'B'), + '2.0.1': setAttr('attributes.c', 'C'), + }, + }), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ id: 'smelly', type: 'dog', attributes: { name: 'Callie' }, - migrationVersion: { dog: '10.2.0' }, - }) - ).toThrow( - /Document "smelly" has property "dog" which belongs to a more recent version of Kibana \[10\.2\.0\]\. The last known version is \[undefined\]/i - ); - }); + migrationVersion: { dog: '1.2.3' }, + }); + expect(actual).toEqual({ + id: 'smelly', + type: 'dog', + attributes: { name: 'Callie', b: 'B', c: 'C' }, + migrationVersion: { dog: '2.0.1' }, + coreMigrationVersion: kibanaVersion, + }); + }); - it('rejects docs that belong to a newer plugin', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'dawg', - migrations: { - '1.2.3': setAttr('attributes.a', 'A'), - }, - }), + it('rejects docs with a migrationVersion[type] for a type that does not have any migrations defined', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + }); + migrator.prepareMigrations(); + expect(() => + migrator.migrate({ + id: 'smelly', + type: 'dog', + attributes: { name: 'Callie' }, + migrationVersion: { dog: '10.2.0' }, + }) + ).toThrow( + /Document "smelly" has property "dog" which belongs to a more recent version of Kibana \[10\.2\.0\]\. The last known version is \[undefined\]/i + ); }); - migrator.prepareMigrations(); - expect(() => - migrator.migrate({ - id: 'fleabag', - type: 'dawg', - attributes: { name: 'Callie' }, - migrationVersion: { dawg: '1.2.4' }, - }) - ).toThrow( - /Document "fleabag" has property "dawg" which belongs to a more recent version of Kibana \[1\.2\.4\]\. The last known version is \[1\.2\.3\]/i - ); - }); - it('applies migrations in order', () => { - let count = 0; - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'dog', - migrations: { - '2.2.4': setAttr('attributes.b', () => ++count), - '10.0.1': setAttr('attributes.c', () => ++count), - '1.2.3': setAttr('attributes.a', () => ++count), - }, - }), + it('rejects docs with a migrationVersion[type] for a type that does not have a migration >= that version defined', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'dawg', + migrations: { + '1.2.3': setAttr('attributes.a', 'A'), + }, + }), + }); + migrator.prepareMigrations(); + expect(() => + migrator.migrate({ + id: 'fleabag', + type: 'dawg', + attributes: { name: 'Callie' }, + migrationVersion: { dawg: '1.2.4' }, + }) + ).toThrow( + /Document "fleabag" has property "dawg" which belongs to a more recent version of Kibana \[1\.2\.4\]\. The last known version is \[1\.2\.3\]/i + ); }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'smelly', - type: 'dog', - attributes: { name: 'Callie' }, - migrationVersion: { dog: '1.2.0' }, + + it('rejects docs that have an invalid coreMigrationVersion', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + kibanaVersion: '8.0.1', + }); + migrator.prepareMigrations(); + expect(() => + migrator.migrate({ + id: 'happy', + type: 'dog', + attributes: { name: 'Callie' }, + coreMigrationVersion: 'not-a-semver', + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Document \\"happy\\" has an invalid \\"coreMigrationVersion\\" [not-a-semver]. This must be a semver value."` + ); }); - expect(actual).toEqual({ - id: 'smelly', - type: 'dog', - attributes: { name: 'Callie', a: 1, b: 2, c: 3 }, - migrationVersion: { dog: '10.0.1' }, + + it('rejects docs that have a coreMigrationVersion higher than the current Kibana version', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + kibanaVersion: '8.0.1', + }); + migrator.prepareMigrations(); + expect(() => + migrator.migrate({ + id: 'wet', + type: 'dog', + attributes: { name: 'Callie' }, + coreMigrationVersion: '8.0.2', + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Document \\"wet\\" has a \\"coreMigrationVersion\\" which belongs to a more recent version of Kibana [8.0.2]. The current version is [8.0.1]."` + ); }); - }); - it('allows props to be added', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry( - { - name: 'animal', - migrations: { - '1.0.0': setAttr('animal', (name: string) => `Animal: ${name}`), - }, - }, - { + it('applies migrations in order', () => { + let count = 0; + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ name: 'dog', migrations: { - '2.2.4': setAttr('animal', 'Doggie'), + '2.2.4': setAttr('attributes.b', () => ++count), + '10.0.1': setAttr('attributes.c', () => ++count), + '1.2.3': setAttr('attributes.a', () => ++count), }, - } - ), - }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'smelly', - type: 'dog', - attributes: { name: 'Callie' }, - migrationVersion: { dog: '1.2.0' }, - }); - expect(actual).toEqual({ - id: 'smelly', - type: 'dog', - attributes: { name: 'Callie' }, - animal: 'Animal: Doggie', - migrationVersion: { animal: '1.0.0', dog: '2.2.4' }, + }), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ + id: 'smelly', + type: 'dog', + attributes: { name: 'Callie' }, + migrationVersion: { dog: '1.2.0' }, + }); + expect(actual).toEqual({ + id: 'smelly', + type: 'dog', + attributes: { name: 'Callie', a: 1, b: 2, c: 3 }, + migrationVersion: { dog: '10.0.1' }, + coreMigrationVersion: kibanaVersion, + }); }); - }); - it('allows props to be renamed', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry( - { - name: 'animal', - migrations: { - '1.0.0': setAttr('animal', (name: string) => `Animal: ${name}`), - '3.2.1': renameAttr('animal', 'dawg'), + it('allows props to be added', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { + name: 'animal', + migrations: { + '1.0.0': setAttr('animal', (name: string) => `Animal: ${name}`), + }, }, - }, - { - name: 'dawg', + { + name: 'dog', + migrations: { + '2.2.4': setAttr('animal', 'Doggie'), + }, + } + ), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ + id: 'smelly', + type: 'dog', + attributes: { name: 'Callie' }, + migrationVersion: { dog: '1.2.0' }, + }); + expect(actual).toEqual({ + id: 'smelly', + type: 'dog', + attributes: { name: 'Callie' }, + animal: 'Animal: Doggie', + migrationVersion: { animal: '1.0.0', dog: '2.2.4' }, + coreMigrationVersion: kibanaVersion, + }); + }); + + it('allows props to be renamed', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'dog', migrations: { - '2.2.4': renameAttr('dawg', 'animal'), - '3.2.0': setAttr('dawg', (name: string) => `Dawg3.x: ${name}`), + '1.0.0': setAttr('attributes.name', (name: string) => `Name: ${name}`), + '1.0.1': renameAttr('attributes.name', 'attributes.title'), + '1.0.2': setAttr('attributes.title', (name: string) => `Title: ${name}`), }, - } - ), + }), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ + id: 'smelly', + type: 'dog', + attributes: { name: 'Callie' }, + migrationVersion: {}, + }); + expect(actual).toEqual({ + id: 'smelly', + type: 'dog', + attributes: { title: 'Title: Name: Callie' }, + migrationVersion: { dog: '1.0.2' }, + coreMigrationVersion: kibanaVersion, + }); }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'smelly', - type: 'foo', - attributes: { name: 'Callie' }, - dawg: 'Yo', - migrationVersion: {}, - } as SavedObjectUnsanitizedDoc); - expect(actual).toEqual({ - id: 'smelly', - type: 'foo', - attributes: { name: 'Callie' }, - dawg: 'Dawg3.x: Animal: Yo', - migrationVersion: { animal: '3.2.1', dawg: '3.2.0' }, + + it('allows changing type', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { + name: 'cat', + migrations: { + '1.0.0': setAttr('attributes.name', (name: string) => `Kitty ${name}`), + }, + }, + { + name: 'dog', + migrations: { + '2.2.4': setAttr('type', 'cat'), + }, + } + ), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ + id: 'smelly', + type: 'dog', + attributes: { name: 'Callie' }, + migrationVersion: {}, + }); + expect(actual).toEqual({ + id: 'smelly', + type: 'cat', + attributes: { name: 'Kitty Callie' }, + migrationVersion: { dog: '2.2.4', cat: '1.0.0' }, + coreMigrationVersion: kibanaVersion, + }); }); - }); - it('allows changing type', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry( - { + it('disallows updating a migrationVersion prop to a lower version', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ name: 'cat', migrations: { - '1.0.0': setAttr('attributes.name', (name: string) => `Kitty ${name}`), + '1.0.0': setAttr('migrationVersion.foo', '3.2.1'), }, - }, - { - name: 'dog', + }), + }); + migrator.prepareMigrations(); + expect(() => + migrator.migrate({ + id: 'smelly', + type: 'cat', + attributes: { name: 'Boo' }, + migrationVersion: { foo: '4.5.6' }, + }) + ).toThrow( + /Migration "cat v 1.0.0" attempted to downgrade "migrationVersion.foo" from 4.5.6 to 3.2.1./ + ); + }); + + it('disallows removing a migrationVersion prop', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'cat', migrations: { - '2.2.4': setAttr('type', 'cat'), + '1.0.0': setAttr('migrationVersion', {}), }, - } - ), - }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'smelly', - type: 'dog', - attributes: { name: 'Callie' }, - migrationVersion: {}, - }); - expect(actual).toEqual({ - id: 'smelly', - type: 'cat', - attributes: { name: 'Kitty Callie' }, - migrationVersion: { dog: '2.2.4', cat: '1.0.0' }, + }), + }); + migrator.prepareMigrations(); + expect(() => + migrator.migrate({ + id: 'smelly', + type: 'cat', + attributes: { name: 'Boo' }, + migrationVersion: { foo: '4.5.6' }, + }) + ).toThrow( + /Migration "cat v 1.0.0" attempted to downgrade "migrationVersion.foo" from 4.5.6 to undefined./ + ); }); - }); - it('disallows updating a migrationVersion prop to a lower version', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'cat', - migrations: { - '1.0.0': setAttr('migrationVersion.foo', '3.2.1'), - }, - }), + it('allows updating a migrationVersion prop to a later version', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'cat', + migrations: { + '1.0.0': setAttr('migrationVersion.cat', '2.9.1'), + '2.0.0': () => { + throw new Error('POW!'); + }, + '2.9.1': () => { + throw new Error('BANG!'); + }, + '3.0.0': setAttr('attributes.name', 'Shiny'), + }, + }), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ + id: 'smelly', + type: 'cat', + attributes: { name: 'Boo' }, + migrationVersion: { cat: '0.5.6' }, + }); + expect(actual).toEqual({ + id: 'smelly', + type: 'cat', + attributes: { name: 'Shiny' }, + migrationVersion: { cat: '3.0.0' }, + coreMigrationVersion: kibanaVersion, + }); }); - migrator.prepareMigrations(); - expect(() => - migrator.migrate({ + it('allows adding props to migrationVersion', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'cat', + migrations: { + '1.0.0': setAttr('migrationVersion.foo', '5.6.7'), + }, + }), + }); + migrator.prepareMigrations(); + const actual = migrator.migrate({ id: 'smelly', type: 'cat', attributes: { name: 'Boo' }, - migrationVersion: { foo: '4.5.6' }, - }) - ).toThrow( - /Migration "cat v 1.0.0" attempted to downgrade "migrationVersion.foo" from 4.5.6 to 3.2.1./ - ); - }); - - it('disallows removing a migrationVersion prop', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'cat', - migrations: { - '1.0.0': setAttr('migrationVersion', {}), - }, - }), - }); - migrator.prepareMigrations(); - expect(() => - migrator.migrate({ + migrationVersion: {}, + }); + expect(actual).toEqual({ id: 'smelly', type: 'cat', attributes: { name: 'Boo' }, - migrationVersion: { foo: '4.5.6' }, - }) - ).toThrow( - /Migration "cat v 1.0.0" attempted to downgrade "migrationVersion.foo" from 4.5.6 to undefined./ - ); - }); - - it('allows updating a migrationVersion prop to a later version', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'cat', - migrations: { - '1.0.0': setAttr('migrationVersion.cat', '2.9.1'), - '2.0.0': () => { - throw new Error('POW!'); - }, - '2.9.1': () => { - throw new Error('BANG!'); - }, - '3.0.0': setAttr('attributes.name', 'Shiny'), - }, - }), - }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'smelly', - type: 'cat', - attributes: { name: 'Boo' }, - migrationVersion: { cat: '0.5.6' }, - }); - expect(actual).toEqual({ - id: 'smelly', - type: 'cat', - attributes: { name: 'Shiny' }, - migrationVersion: { cat: '3.0.0' }, + migrationVersion: { cat: '1.0.0', foo: '5.6.7' }, + coreMigrationVersion: kibanaVersion, + }); }); - }); - it('allows adding props to migrationVersion', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'cat', - migrations: { - '1.0.0': setAttr('migrationVersion.foo', '5.6.7'), - }, - }), - }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'smelly', - type: 'cat', - attributes: { name: 'Boo' }, - migrationVersion: {}, - }); - expect(actual).toEqual({ - id: 'smelly', - type: 'cat', - attributes: { name: 'Boo' }, - migrationVersion: { cat: '1.0.0', foo: '5.6.7' }, - }); - }); - - it('logs the document and transform that failed', () => { - const log = mockLogger; - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'dog', - migrations: { - '1.2.3': () => { - throw new Error('Dang diggity!'); + it('logs the document and transform that failed', () => { + const log = mockLogger; + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'dog', + migrations: { + '1.2.3': () => { + throw new Error('Dang diggity!'); + }, }, - }, - }), - log, - }); - const failedDoc = { - id: 'smelly', - type: 'dog', - attributes: {}, - migrationVersion: {}, - }; - try { + }), + log, + }); migrator.prepareMigrations(); - migrator.migrate(_.cloneDeep(failedDoc)); - expect('Did not throw').toEqual('But it should have!'); - } catch (error) { - expect(error.message).toMatch(/Dang diggity!/); - const warning = loggingSystemMock.collect(mockLoggerFactory).warn[0][0]; - expect(warning).toContain(JSON.stringify(failedDoc)); - expect(warning).toContain('dog:1.2.3'); - } - }); + const failedDoc = { + id: 'smelly', + type: 'dog', + attributes: {}, + migrationVersion: {}, + }; + try { + migrator.migrate(_.cloneDeep(failedDoc)); + expect('Did not throw').toEqual('But it should have!'); + } catch (error) { + expect(error.message).toMatch(/Dang diggity!/); + const warning = loggingSystemMock.collect(mockLoggerFactory).warn[0][0]; + expect(warning).toContain(JSON.stringify(failedDoc)); + expect(warning).toContain('dog:1.2.3'); + } + }); - it('logs message in transform function', () => { - const logTestMsg = '...said the joker to the thief'; - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'dog', - migrations: { - '1.2.3': (doc, { log }) => { - log.info(logTestMsg); - log.warning(logTestMsg); - return doc; + it('logs message in transform function', () => { + const logTestMsg = '...said the joker to the thief'; + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'dog', + migrations: { + '1.2.3': (doc, { log }) => { + log.info(logTestMsg); + log.warning(logTestMsg); + return doc; + }, }, - }, - }), - log: mockLogger, + }), + log: mockLogger, + }); + migrator.prepareMigrations(); + const doc = { + id: 'joker', + type: 'dog', + attributes: {}, + migrationVersion: {}, + }; + migrator.migrate(doc); + expect(loggingSystemMock.collect(mockLoggerFactory).info[0][0]).toEqual(logTestMsg); + expect(loggingSystemMock.collect(mockLoggerFactory).warn[1][0]).toEqual(logTestMsg); }); - const doc = { - id: 'joker', - type: 'dog', - attributes: {}, - migrationVersion: {}, - }; - migrator.prepareMigrations(); - migrator.migrate(doc); - expect(loggingSystemMock.collect(mockLoggerFactory).info[0][0]).toEqual(logTestMsg); - expect(loggingSystemMock.collect(mockLoggerFactory).warn[1][0]).toEqual(logTestMsg); - }); - test('extracts the latest migration version info', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry( - { - name: 'aaa', - migrations: { - '1.2.3': (doc: SavedObjectUnsanitizedDoc) => doc, - '10.4.0': (doc: SavedObjectUnsanitizedDoc) => doc, - '2.2.1': (doc: SavedObjectUnsanitizedDoc) => doc, + test('extracts the latest migration version info', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { + name: 'aaa', + migrations: { + '1.2.3': (doc: SavedObjectUnsanitizedDoc) => doc, + '10.4.0': (doc: SavedObjectUnsanitizedDoc) => doc, + '2.2.1': (doc: SavedObjectUnsanitizedDoc) => doc, + }, }, - }, - { - name: 'bbb', - migrations: { - '3.2.3': (doc: SavedObjectUnsanitizedDoc) => doc, - '2.0.0': (doc: SavedObjectUnsanitizedDoc) => doc, + { + name: 'bbb', + migrations: { + '3.2.3': (doc: SavedObjectUnsanitizedDoc) => doc, + '2.0.0': (doc: SavedObjectUnsanitizedDoc) => doc, + }, }, - } - ), + { + name: 'ccc', + namespaceType: 'multiple', + migrations: { + '9.0.0': (doc: SavedObjectUnsanitizedDoc) => doc, + }, + convertToMultiNamespaceTypeVersion: '11.0.0', // this results in reference transforms getting added to other types, but does not increase the migrationVersion of those types + } + ), + }); + migrator.prepareMigrations(); + expect(migrator.migrationVersion).toEqual({ + aaa: '10.4.0', + bbb: '3.2.3', + ccc: '11.0.0', + }); }); - migrator.prepareMigrations(); - expect(migrator.migrationVersion).toEqual({ - aaa: '10.4.0', - bbb: '3.2.3', + describe('conversion to multi-namespace type', () => { + it('assumes documents w/ undefined migrationVersion and correct coreMigrationVersion are up to date', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { name: 'dog', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' } + // no migration transforms are defined, the migrationVersion will be derived from 'convertToMultiNamespaceTypeVersion' + ), + }); + migrator.prepareMigrations(); + const obj = { + id: 'mischievous', + type: 'dog', + attributes: { name: 'Ann' }, + coreMigrationVersion: kibanaVersion, + } as SavedObjectUnsanitizedDoc; + const actual = migrator.migrateAndConvert(obj); + expect(actual).toEqual([ + { + id: 'mischievous', + type: 'dog', + attributes: { name: 'Ann' }, + migrationVersion: { dog: '1.0.0' }, + coreMigrationVersion: kibanaVersion, + // there is no 'namespaces' field because no transforms were applied; this scenario is contrived for a clean test case but is not indicative of a real-world scenario + }, + ]); + }); + + it('skips reference transforms and conversion transforms when using `migrate`', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { name: 'dog', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' }, + { name: 'toy', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' } + ), + }); + migrator.prepareMigrations(); + const obj = { + id: 'cowardly', + type: 'dog', + attributes: { name: 'Leslie' }, + migrationVersion: {}, + references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], + namespace: 'foo-namespace', + }; + const actual = migrator.migrate(obj); + expect(mockUuidv5).not.toHaveBeenCalled(); + expect(actual).toEqual({ + id: 'cowardly', + type: 'dog', + attributes: { name: 'Leslie' }, + migrationVersion: { dog: '1.0.0' }, + references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], + coreMigrationVersion: kibanaVersion, + namespace: 'foo-namespace', + // there is no 'namespaces' field because no conversion transform was applied; this scenario is contrived for a clean test case but is not indicative of a real-world scenario + }); + }); + + describe('correctly applies reference transforms', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { name: 'dog', namespaceType: 'single' }, + { name: 'toy', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' } + ), + }); + migrator.prepareMigrations(); + const obj = { + id: 'bad', + type: 'dog', + attributes: { name: 'Sweet Peach' }, + migrationVersion: {}, + references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], + }; + + it('in the default space', () => { + const actual = migrator.migrateAndConvert(obj); + expect(mockUuidv5).not.toHaveBeenCalled(); + expect(actual).toEqual([ + { + id: 'bad', + type: 'dog', + attributes: { name: 'Sweet Peach' }, + references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change + coreMigrationVersion: kibanaVersion, + }, + ]); + }); + + it('in a non-default space', () => { + const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' }); + expect(mockUuidv5).toHaveBeenCalledTimes(1); + expect(mockUuidv5).toHaveBeenCalledWith('foo-namespace:toy:favorite', 'DNSUUID'); + expect(actual).toEqual([ + { + id: 'bad', + type: 'dog', + attributes: { name: 'Sweet Peach' }, + references: [{ id: 'uuidv5', type: 'toy', name: 'BALL!' }], // changed + coreMigrationVersion: kibanaVersion, + namespace: 'foo-namespace', + }, + ]); + }); + }); + + describe('correctly applies conversion transforms', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'dog', + namespaceType: 'multiple', + convertToMultiNamespaceTypeVersion: '1.0.0', + }), + }); + migrator.prepareMigrations(); + const obj = { + id: 'loud', + type: 'dog', + attributes: { name: 'Wally' }, + migrationVersion: {}, + }; + + it('in the default space', () => { + const actual = migrator.migrateAndConvert(obj); + expect(mockUuidv5).not.toHaveBeenCalled(); + expect(actual).toEqual([ + { + id: 'loud', + type: 'dog', + attributes: { name: 'Wally' }, + migrationVersion: { dog: '1.0.0' }, + coreMigrationVersion: kibanaVersion, + namespaces: ['default'], + }, + ]); + }); + + it('in a non-default space', () => { + const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' }); + expect(mockUuidv5).toHaveBeenCalledTimes(1); + expect(mockUuidv5).toHaveBeenCalledWith('foo-namespace:dog:loud', 'DNSUUID'); + expect(actual).toEqual([ + { + id: 'uuidv5', + type: 'dog', + attributes: { name: 'Wally' }, + migrationVersion: { dog: '1.0.0' }, + coreMigrationVersion: kibanaVersion, + namespaces: ['foo-namespace'], + originId: 'loud', + }, + { + id: 'foo-namespace:dog:loud', + type: LEGACY_URL_ALIAS_TYPE, + attributes: { + targetNamespace: 'foo-namespace', + targetType: 'dog', + targetId: 'uuidv5', + }, + migrationVersion: {}, + coreMigrationVersion: kibanaVersion, + }, + ]); + }); + }); + + describe('correctly applies reference and conversion transforms', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { name: 'dog', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' }, + { name: 'toy', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' } + ), + }); + migrator.prepareMigrations(); + const obj = { + id: 'cute', + type: 'dog', + attributes: { name: 'Too' }, + migrationVersion: {}, + references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], + }; + + it('in the default space', () => { + const actual = migrator.migrateAndConvert(obj); + expect(mockUuidv5).not.toHaveBeenCalled(); + expect(actual).toEqual([ + { + id: 'cute', + type: 'dog', + attributes: { name: 'Too' }, + migrationVersion: { dog: '1.0.0' }, + references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change + coreMigrationVersion: kibanaVersion, + namespaces: ['default'], + }, + ]); + }); + + it('in a non-default space', () => { + const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' }); + expect(mockUuidv5).toHaveBeenCalledTimes(2); + expect(mockUuidv5).toHaveBeenNthCalledWith(1, 'foo-namespace:toy:favorite', 'DNSUUID'); + expect(mockUuidv5).toHaveBeenNthCalledWith(2, 'foo-namespace:dog:cute', 'DNSUUID'); + expect(actual).toEqual([ + { + id: 'uuidv5', + type: 'dog', + attributes: { name: 'Too' }, + migrationVersion: { dog: '1.0.0' }, + references: [{ id: 'uuidv5', type: 'toy', name: 'BALL!' }], // changed + coreMigrationVersion: kibanaVersion, + namespaces: ['foo-namespace'], + originId: 'cute', + }, + { + id: 'foo-namespace:dog:cute', + type: LEGACY_URL_ALIAS_TYPE, + attributes: { + targetNamespace: 'foo-namespace', + targetType: 'dog', + targetId: 'uuidv5', + }, + migrationVersion: {}, + coreMigrationVersion: kibanaVersion, + }, + ]); + }); + }); + + describe('correctly applies reference and migration transforms', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { + name: 'dog', + namespaceType: 'single', + migrations: { + '1.0.0': setAttr('migrationVersion.dog', '2.0.0'), + '2.0.0': (doc) => doc, // noop + }, + }, + { name: 'toy', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' } + ), + }); + migrator.prepareMigrations(); + const obj = { + id: 'sleepy', + type: 'dog', + attributes: { name: 'Patches' }, + migrationVersion: {}, + references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], + }; + + it('in the default space', () => { + const actual = migrator.migrateAndConvert(obj); + expect(mockUuidv5).not.toHaveBeenCalled(); + expect(actual).toEqual([ + { + id: 'sleepy', + type: 'dog', + attributes: { name: 'Patches' }, + migrationVersion: { dog: '2.0.0' }, + references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change + coreMigrationVersion: kibanaVersion, + }, + ]); + }); + + it('in a non-default space', () => { + const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' }); + expect(mockUuidv5).toHaveBeenCalledTimes(1); + expect(mockUuidv5).toHaveBeenCalledWith('foo-namespace:toy:favorite', 'DNSUUID'); + expect(actual).toEqual([ + { + id: 'sleepy', + type: 'dog', + attributes: { name: 'Patches' }, + migrationVersion: { dog: '2.0.0' }, + references: [{ id: 'uuidv5', type: 'toy', name: 'BALL!' }], // changed + coreMigrationVersion: kibanaVersion, + namespace: 'foo-namespace', + }, + ]); + }); + }); + + describe('correctly applies conversion and migration transforms', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry({ + name: 'dog', + namespaceType: 'multiple', + migrations: { + '1.0.0': setAttr('migrationVersion.dog', '2.0.0'), + '2.0.0': (doc) => doc, // noop + }, + convertToMultiNamespaceTypeVersion: '1.0.0', // the conversion transform occurs before the migration transform above + }), + }); + migrator.prepareMigrations(); + const obj = { + id: 'hungry', + type: 'dog', + attributes: { name: 'Remy' }, + migrationVersion: {}, + }; + + it('in the default space', () => { + const actual = migrator.migrateAndConvert(obj); + expect(mockUuidv5).not.toHaveBeenCalled(); + expect(actual).toEqual([ + { + id: 'hungry', + type: 'dog', + attributes: { name: 'Remy' }, + migrationVersion: { dog: '2.0.0' }, + coreMigrationVersion: kibanaVersion, + namespaces: ['default'], + }, + ]); + }); + + it('in a non-default space', () => { + const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' }); + expect(mockUuidv5).toHaveBeenCalledTimes(1); + expect(mockUuidv5).toHaveBeenCalledWith('foo-namespace:dog:hungry', 'DNSUUID'); + expect(actual).toEqual([ + { + id: 'uuidv5', + type: 'dog', + attributes: { name: 'Remy' }, + migrationVersion: { dog: '2.0.0' }, + coreMigrationVersion: kibanaVersion, + namespaces: ['foo-namespace'], + originId: 'hungry', + }, + { + id: 'foo-namespace:dog:hungry', + type: LEGACY_URL_ALIAS_TYPE, + attributes: { + targetNamespace: 'foo-namespace', + targetType: 'dog', + targetId: 'uuidv5', + }, + migrationVersion: {}, + coreMigrationVersion: kibanaVersion, + }, + ]); + }); + }); + + describe('correctly applies reference, conversion, and migration transforms', () => { + const migrator = new DocumentMigrator({ + ...testOpts(), + typeRegistry: createRegistry( + { + name: 'dog', + namespaceType: 'multiple', + migrations: { + '1.0.0': setAttr('migrationVersion.dog', '2.0.0'), + '2.0.0': (doc) => doc, // noop + }, + convertToMultiNamespaceTypeVersion: '1.0.0', + }, + { name: 'toy', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' } + ), + }); + migrator.prepareMigrations(); + const obj = { + id: 'pretty', + type: 'dog', + attributes: { name: 'Sasha' }, + migrationVersion: {}, + references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], + }; + + it('in the default space', () => { + const actual = migrator.migrateAndConvert(obj); + expect(mockUuidv5).not.toHaveBeenCalled(); + expect(actual).toEqual([ + { + id: 'pretty', + type: 'dog', + attributes: { name: 'Sasha' }, + migrationVersion: { dog: '2.0.0' }, + references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change + coreMigrationVersion: kibanaVersion, + namespaces: ['default'], + }, + ]); + }); + + it('in a non-default space', () => { + const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' }); + expect(mockUuidv5).toHaveBeenCalledTimes(2); + expect(mockUuidv5).toHaveBeenNthCalledWith(1, 'foo-namespace:toy:favorite', 'DNSUUID'); + expect(mockUuidv5).toHaveBeenNthCalledWith(2, 'foo-namespace:dog:pretty', 'DNSUUID'); + expect(actual).toEqual([ + { + id: 'uuidv5', + type: 'dog', + attributes: { name: 'Sasha' }, + migrationVersion: { dog: '2.0.0' }, + references: [{ id: 'uuidv5', type: 'toy', name: 'BALL!' }], // changed + coreMigrationVersion: kibanaVersion, + namespaces: ['foo-namespace'], + originId: 'pretty', + }, + { + id: 'foo-namespace:dog:pretty', + type: LEGACY_URL_ALIAS_TYPE, + attributes: { + targetNamespace: 'foo-namespace', + targetType: 'dog', + targetId: 'uuidv5', + }, + migrationVersion: {}, + coreMigrationVersion: kibanaVersion, + }, + ]); + }); + }); }); }); }); diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.ts b/src/core/server/saved_objects/migrations/core/document_migrator.ts index 04e9a4e165f96..e4b89a949d3cf 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -50,50 +50,102 @@ */ import Boom from '@hapi/boom'; +import uuidv5 from 'uuid/v5'; import { set } from '@elastic/safer-lodash-set'; import _ from 'lodash'; import Semver from 'semver'; import { Logger } from '../../../logging'; import { SavedObjectUnsanitizedDoc } from '../../serialization'; -import { SavedObjectsMigrationVersion } from '../../types'; +import { + SavedObjectsMigrationVersion, + SavedObjectsNamespaceType, + SavedObjectsType, +} from '../../types'; import { MigrationLogger } from './migration_logger'; import { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { SavedObjectMigrationFn, SavedObjectMigrationMap } from '../types'; +import { DEFAULT_NAMESPACE_STRING } from '../../service/lib/utils'; +import { LegacyUrlAlias, LEGACY_URL_ALIAS_TYPE } from '../../object_types'; -export type TransformFn = (doc: SavedObjectUnsanitizedDoc) => SavedObjectUnsanitizedDoc; +const DEFAULT_MINIMUM_CONVERT_VERSION = '8.0.0'; + +export type MigrateFn = (doc: SavedObjectUnsanitizedDoc) => SavedObjectUnsanitizedDoc; +export type MigrateAndConvertFn = (doc: SavedObjectUnsanitizedDoc) => SavedObjectUnsanitizedDoc[]; + +interface TransformResult { + /** + * This is the original document that has been transformed. + */ + transformedDoc: SavedObjectUnsanitizedDoc; + /** + * These are any new document(s) that have been created during the transformation process; these are not transformed, but they are marked + * as up-to-date. Only conversion transforms generate additional documents. + */ + additionalDocs: SavedObjectUnsanitizedDoc[]; +} + +type ApplyTransformsFn = ( + doc: SavedObjectUnsanitizedDoc, + options?: TransformOptions +) => TransformResult; + +interface TransformOptions { + convertNamespaceTypes?: boolean; +} interface DocumentMigratorOptions { kibanaVersion: string; typeRegistry: ISavedObjectTypeRegistry; + minimumConvertVersion?: string; log: Logger; } interface ActiveMigrations { [type: string]: { - latestVersion: string; - transforms: Array<{ - version: string; - transform: TransformFn; - }>; + /** Derived from `migrate` transforms and `convert` transforms */ + latestMigrationVersion?: string; + /** Derived from `reference` transforms */ + latestCoreMigrationVersion?: string; + transforms: Transform[]; }; } +interface Transform { + version: string; + transform: (doc: SavedObjectUnsanitizedDoc) => TransformResult; + /** + * There are two "migrationVersion" transform types: + * * `migrate` - These transforms are defined and added by consumers using the type registry; each is applied to a single object type + * based on an object's `migrationVersion[type]` field. These are applied during index migrations and document migrations. + * * `convert` - These transforms are defined by core and added by consumers using the type registry; each is applied to a single object + * type based on an object's `migrationVersion[type]` field. These are applied during index migrations, NOT document migrations. + * + * There is one "coreMigrationVersion" transform type: + * * `reference` - These transforms are defined by core and added by consumers using the type registry; they are applied to all object + * types based on their `coreMigrationVersion` field. These are applied during index migrations, NOT document migrations. + * + * If any additional transform types are added, the functions below should be updated to account for them. + */ + transformType: 'migrate' | 'convert' | 'reference'; +} + /** * Manages migration of individual documents. */ export interface VersionedTransformer { migrationVersion: SavedObjectsMigrationVersion; + migrate: MigrateFn; + migrateAndConvert: MigrateAndConvertFn; prepareMigrations: () => void; - migrate: TransformFn; } /** * A concrete implementation of the VersionedTransformer interface. */ export class DocumentMigrator implements VersionedTransformer { - private documentMigratorOptions: DocumentMigratorOptions; + private documentMigratorOptions: Omit; private migrations?: ActiveMigrations; - private transformDoc?: TransformFn; + private transformDoc?: ApplyTransformsFn; /** * Creates an instance of DocumentMigrator. @@ -101,11 +153,18 @@ export class DocumentMigrator implements VersionedTransformer { * @param {DocumentMigratorOptions} opts * @prop {string} kibanaVersion - The current version of Kibana * @prop {SavedObjectTypeRegistry} typeRegistry - The type registry to get type migrations from + * @prop {string} minimumConvertVersion - The minimum version of Kibana in which documents can be converted to multi-namespace types * @prop {Logger} log - The migration logger * @memberof DocumentMigrator */ - constructor({ typeRegistry, kibanaVersion, log }: DocumentMigratorOptions) { - validateMigrationDefinition(typeRegistry); + constructor({ + typeRegistry, + kibanaVersion, + minimumConvertVersion = DEFAULT_MINIMUM_CONVERT_VERSION, + log, + }: DocumentMigratorOptions) { + validateMigrationDefinition(typeRegistry, kibanaVersion, minimumConvertVersion); + this.documentMigratorOptions = { typeRegistry, kibanaVersion, log }; } @@ -120,7 +179,14 @@ export class DocumentMigrator implements VersionedTransformer { if (!this.migrations) { throw new Error('Migrations are not ready. Make sure prepareMigrations is called first.'); } - return _.mapValues(this.migrations, ({ latestVersion }) => latestVersion); + + return Object.entries(this.migrations).reduce((acc, [prop, { latestMigrationVersion }]) => { + // some migration objects won't have a latestMigrationVersion (they only contain reference transforms that are applied from other types) + if (latestMigrationVersion) { + return { ...acc, [prop]: latestMigrationVersion }; + } + return acc; + }, {}); } /** @@ -132,7 +198,7 @@ export class DocumentMigrator implements VersionedTransformer { public prepareMigrations = () => { const { typeRegistry, kibanaVersion, log } = this.documentMigratorOptions; - this.migrations = buildActiveMigrations(typeRegistry, log); + this.migrations = buildActiveMigrations(typeRegistry, kibanaVersion, log); this.transformDoc = buildDocumentTransform({ kibanaVersion, migrations: this.migrations, @@ -155,25 +221,56 @@ export class DocumentMigrator implements VersionedTransformer { // Ex: Importing sample data that is cached at import level, migrations would // execute on mutated data the second time. const clonedDoc = _.cloneDeep(doc); - return this.transformDoc(clonedDoc); + const { transformedDoc } = this.transformDoc(clonedDoc); + return transformedDoc; + }; + + /** + * Migrates a document to the latest version and applies type conversions if applicable. Also returns any additional document(s) that may + * have been created during the transformation process. + * + * @param {SavedObjectUnsanitizedDoc} doc + * @returns {SavedObjectUnsanitizedDoc} + * @memberof DocumentMigrator + */ + public migrateAndConvert = (doc: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc[] => { + if (!this.migrations || !this.transformDoc) { + throw new Error('Migrations are not ready. Make sure prepareMigrations is called first.'); + } + + // Clone the document to prevent accidental mutations on the original data + // Ex: Importing sample data that is cached at import level, migrations would + // execute on mutated data the second time. + const clonedDoc = _.cloneDeep(doc); + const { transformedDoc, additionalDocs } = this.transformDoc(clonedDoc, { + convertNamespaceTypes: true, + }); + return [transformedDoc, ...additionalDocs]; }; } -function validateMigrationsMapObject(name: string, migrationsMap?: SavedObjectMigrationMap) { +function validateMigrationsMapObject( + name: string, + kibanaVersion: string, + migrationsMap?: SavedObjectMigrationMap +) { function assertObject(obj: any, prefix: string) { if (!obj || typeof obj !== 'object') { throw new Error(`${prefix} Got ${obj}.`); } } - function assertValidSemver(version: string, type: string) { if (!Semver.valid(version)) { throw new Error( `Invalid migration for type ${type}. Expected all properties to be semvers, but got ${version}.` ); } + if (Semver.gt(version, kibanaVersion)) { + throw new Error( + `Invalid migration for type ${type}. Property '${version}' cannot be greater than the current Kibana version '${kibanaVersion}'.` + ); + } } - function assertValidTransform(fn: any, version: string, type: string) { if (typeof fn !== 'function') { throw new Error(`Invalid migration ${type}.${version}: expected a function, but got ${fn}.`); @@ -194,23 +291,63 @@ function validateMigrationsMapObject(name: string, migrationsMap?: SavedObjectMi } /** - * Basic validation that the migraiton definition matches our expectations. We can't + * Basic validation that the migration definition matches our expectations. We can't * rely on TypeScript here, as the caller may be JavaScript / ClojureScript / any compile-to-js * language. So, this is just to provide a little developer-friendly error messaging. Joi was * giving weird errors, so we're just doing manual validation. */ -function validateMigrationDefinition(registry: ISavedObjectTypeRegistry) { +function validateMigrationDefinition( + registry: ISavedObjectTypeRegistry, + kibanaVersion: string, + minimumConvertVersion: string +) { function assertObjectOrFunction(entity: any, prefix: string) { if (!entity || (typeof entity !== 'function' && typeof entity !== 'object')) { throw new Error(`${prefix} Got! ${typeof entity}, ${JSON.stringify(entity)}.`); } } + function assertValidConvertToMultiNamespaceType( + namespaceType: SavedObjectsNamespaceType, + convertToMultiNamespaceTypeVersion: string, + type: string + ) { + if (namespaceType !== 'multiple') { + throw new Error( + `Invalid convertToMultiNamespaceTypeVersion for type ${type}. Expected namespaceType to be 'multiple', but got '${namespaceType}'.` + ); + } else if (!Semver.valid(convertToMultiNamespaceTypeVersion)) { + throw new Error( + `Invalid convertToMultiNamespaceTypeVersion for type ${type}. Expected value to be a semver, but got '${convertToMultiNamespaceTypeVersion}'.` + ); + } else if (Semver.lt(convertToMultiNamespaceTypeVersion, minimumConvertVersion)) { + throw new Error( + `Invalid convertToMultiNamespaceTypeVersion for type ${type}. Value '${convertToMultiNamespaceTypeVersion}' cannot be less than '${minimumConvertVersion}'.` + ); + } else if (Semver.gt(convertToMultiNamespaceTypeVersion, kibanaVersion)) { + throw new Error( + `Invalid convertToMultiNamespaceTypeVersion for type ${type}. Value '${convertToMultiNamespaceTypeVersion}' cannot be greater than the current Kibana version '${kibanaVersion}'.` + ); + } else if (Semver.patch(convertToMultiNamespaceTypeVersion)) { + throw new Error( + `Invalid convertToMultiNamespaceTypeVersion for type ${type}. Value '${convertToMultiNamespaceTypeVersion}' cannot be used on a patch version (must be like 'x.y.0').` + ); + } + } + registry.getAllTypes().forEach((type) => { - if (type.migrations) { + const { name, migrations, convertToMultiNamespaceTypeVersion, namespaceType } = type; + if (migrations) { assertObjectOrFunction( type.migrations, - `Migration for type ${type.name} should be an object or a function returning an object like { '2.0.0': (doc) => doc }.` + `Migration for type ${name} should be an object or a function returning an object like { '2.0.0': (doc) => doc }.` + ); + } + if (convertToMultiNamespaceTypeVersion) { + assertValidConvertToMultiNamespaceType( + namespaceType, + convertToMultiNamespaceTypeVersion, + name ); } }); @@ -220,74 +357,144 @@ function validateMigrationDefinition(registry: ISavedObjectTypeRegistry) { * Converts migrations from a format that is convenient for callers to a format that * is convenient for our internal usage: * From: { type: { version: fn } } - * To: { type: { latestVersion: string, transforms: [{ version: string, transform: fn }] } } + * To: { type: { latestMigrationVersion?: string; latestCoreMigrationVersion?: string; transforms: [{ version: string, transform: fn }] } } */ function buildActiveMigrations( typeRegistry: ISavedObjectTypeRegistry, + kibanaVersion: string, log: Logger ): ActiveMigrations { - const typesWithMigrationMaps = typeRegistry - .getAllTypes() - .map((type) => ({ - ...type, - migrationsMap: typeof type.migrations === 'function' ? type.migrations() : type.migrations, - })) - .filter((type) => typeof type.migrationsMap !== 'undefined'); - - typesWithMigrationMaps.forEach((type) => - validateMigrationsMapObject(type.name, type.migrationsMap) - ); + const referenceTransforms = getReferenceTransforms(typeRegistry); + + return typeRegistry.getAllTypes().reduce((migrations, type) => { + const migrationsMap = + typeof type.migrations === 'function' ? type.migrations() : type.migrations; + validateMigrationsMapObject(type.name, kibanaVersion, migrationsMap); + + const migrationTransforms = Object.entries(migrationsMap ?? {}).map( + ([version, transform]) => ({ + version, + transform: wrapWithTry(version, type.name, transform, log), + transformType: 'migrate', + }) + ); + const conversionTransforms = getConversionTransforms(type); + const transforms = [ + ...referenceTransforms, + ...conversionTransforms, + ...migrationTransforms, + ].sort(transformComparator); + + if (!transforms.length) { + return migrations; + } - return typesWithMigrationMaps - .filter((type) => type.migrationsMap && Object.keys(type.migrationsMap).length > 0) - .reduce((migrations, type) => { - const transforms = Object.entries(type.migrationsMap!) - .map(([version, transform]) => ({ - version, - transform: wrapWithTry(version, type.name, transform, log), - })) - .sort((a, b) => Semver.compare(a.version, b.version)); - return { - ...migrations, - [type.name]: { - latestVersion: _.last(transforms)!.version, - transforms, - }, - }; - }, {} as ActiveMigrations); + const migrationVersionTransforms: Transform[] = []; + const coreMigrationVersionTransforms: Transform[] = []; + transforms.forEach((x) => { + if (x.transformType === 'migrate' || x.transformType === 'convert') { + migrationVersionTransforms.push(x); + } else { + coreMigrationVersionTransforms.push(x); + } + }); + + return { + ...migrations, + [type.name]: { + latestMigrationVersion: _.last(migrationVersionTransforms)?.version, + latestCoreMigrationVersion: _.last(coreMigrationVersionTransforms)?.version, + transforms, + }, + }; + }, {} as ActiveMigrations); } + /** * Creates a function which migrates and validates any document that is passed to it. */ function buildDocumentTransform({ + kibanaVersion, migrations, }: { kibanaVersion: string; migrations: ActiveMigrations; -}): TransformFn { - return function transformAndValidate(doc: SavedObjectUnsanitizedDoc) { - const result = doc.migrationVersion - ? applyMigrations(doc, migrations) - : markAsUpToDate(doc, migrations); +}): ApplyTransformsFn { + return function transformAndValidate( + doc: SavedObjectUnsanitizedDoc, + options: TransformOptions = {} + ) { + validateCoreMigrationVersion(doc, kibanaVersion); + + const { convertNamespaceTypes = false } = options; + let transformedDoc: SavedObjectUnsanitizedDoc; + let additionalDocs: SavedObjectUnsanitizedDoc[] = []; + if (doc.migrationVersion) { + const result = applyMigrations(doc, migrations, kibanaVersion, convertNamespaceTypes); + transformedDoc = result.transformedDoc; + additionalDocs = additionalDocs.concat( + result.additionalDocs.map((x) => markAsUpToDate(x, migrations, kibanaVersion)) + ); + } else { + transformedDoc = markAsUpToDate(doc, migrations, kibanaVersion); + } // In order to keep tests a bit more stable, we won't // tack on an empy migrationVersion to docs that have // no migrations defined. - if (_.isEmpty(result.migrationVersion)) { - delete result.migrationVersion; + if (_.isEmpty(transformedDoc.migrationVersion)) { + delete transformedDoc.migrationVersion; } - return result; + return { transformedDoc, additionalDocs }; }; } -function applyMigrations(doc: SavedObjectUnsanitizedDoc, migrations: ActiveMigrations) { +function validateCoreMigrationVersion(doc: SavedObjectUnsanitizedDoc, kibanaVersion: string) { + const { id, coreMigrationVersion: docVersion } = doc; + if (!docVersion) { + return; + } + + // We verify that the object's coreMigrationVersion is valid, and that it is not greater than the version supported by Kibana. + // If we have a coreMigrationVersion and the kibanaVersion is smaller than it or does not exist, we are dealing with a document that + // belongs to a future Kibana / plugin version. + if (!Semver.valid(docVersion)) { + throw Boom.badData( + `Document "${id}" has an invalid "coreMigrationVersion" [${docVersion}]. This must be a semver value.`, + doc + ); + } + + if (doc.coreMigrationVersion && Semver.gt(docVersion, kibanaVersion)) { + throw Boom.badData( + `Document "${id}" has a "coreMigrationVersion" which belongs to a more recent version` + + ` of Kibana [${docVersion}]. The current version is [${kibanaVersion}].`, + doc + ); + } +} + +function applyMigrations( + doc: SavedObjectUnsanitizedDoc, + migrations: ActiveMigrations, + kibanaVersion: string, + convertNamespaceTypes: boolean +) { + let additionalDocs: SavedObjectUnsanitizedDoc[] = []; while (true) { const prop = nextUnmigratedProp(doc, migrations); if (!prop) { - return doc; + // regardless of whether or not any reference transform was applied, update the coreMigrationVersion + // this is needed to ensure that newly created documents have an up-to-date coreMigrationVersion field + return { + transformedDoc: { ...doc, coreMigrationVersion: kibanaVersion }, + additionalDocs, + }; } - doc = migrateProp(doc, prop, migrations); + const result = migrateProp(doc, prop, migrations, convertNamespaceTypes); + doc = result.transformedDoc; + additionalDocs = [...additionalDocs, ...result.additionalDocs]; } } @@ -303,7 +510,7 @@ function props(doc: SavedObjectUnsanitizedDoc) { */ function propVersion(doc: SavedObjectUnsanitizedDoc | ActiveMigrations, prop: string) { return ( - ((doc as any)[prop] && (doc as any)[prop].latestVersion) || + ((doc as any)[prop] && (doc as any)[prop].latestMigrationVersion) || (doc.migrationVersion && (doc as any).migrationVersion[prop]) ); } @@ -311,16 +518,137 @@ function propVersion(doc: SavedObjectUnsanitizedDoc | ActiveMigrations, prop: st /** * Sets the doc's migrationVersion to be the most recent version */ -function markAsUpToDate(doc: SavedObjectUnsanitizedDoc, migrations: ActiveMigrations) { +function markAsUpToDate( + doc: SavedObjectUnsanitizedDoc, + migrations: ActiveMigrations, + kibanaVersion: string +) { return { ...doc, migrationVersion: props(doc).reduce((acc, prop) => { const version = propVersion(migrations, prop); return version ? set(acc, prop, version) : acc; }, {}), + coreMigrationVersion: kibanaVersion, }; } +/** + * Converts a single-namespace object to a multi-namespace object. This primarily entails removing the `namespace` field and adding the + * `namespaces` field. + * + * If the object does not exist in the default namespace (undefined), its ID is also regenerated, and an "originId" is added to preserve + * legacy import/copy behavior. + */ +function convertNamespaceType(doc: SavedObjectUnsanitizedDoc) { + const { namespace, ...otherAttrs } = doc; + const additionalDocs: SavedObjectUnsanitizedDoc[] = []; + + // If this object exists in the default namespace, return it with the appropriate `namespaces` field without changing its ID. + if (namespace === undefined) { + return { + transformedDoc: { ...otherAttrs, namespaces: [DEFAULT_NAMESPACE_STRING] }, + additionalDocs, + }; + } + + const { id: originId, type } = otherAttrs; + const id = deterministicallyRegenerateObjectId(namespace, type, originId!); + if (namespace !== undefined) { + const legacyUrlAlias: SavedObjectUnsanitizedDoc = { + id: `${namespace}:${type}:${originId}`, + type: LEGACY_URL_ALIAS_TYPE, + attributes: { + targetNamespace: namespace, + targetType: type, + targetId: id, + }, + }; + additionalDocs.push(legacyUrlAlias); + } + return { + transformedDoc: { ...otherAttrs, id, originId, namespaces: [namespace] }, + additionalDocs, + }; +} + +/** + * Returns all applicable conversion transforms for a given object type. + */ +function getConversionTransforms(type: SavedObjectsType): Transform[] { + const { convertToMultiNamespaceTypeVersion } = type; + if (!convertToMultiNamespaceTypeVersion) { + return []; + } + return [ + { + version: convertToMultiNamespaceTypeVersion, + transform: convertNamespaceType, + transformType: 'convert', + }, + ]; +} + +/** + * Returns all applicable reference transforms for all object types. + */ +function getReferenceTransforms(typeRegistry: ISavedObjectTypeRegistry): Transform[] { + const transformMap = typeRegistry + .getAllTypes() + .filter((type) => type.convertToMultiNamespaceTypeVersion) + .reduce((acc, { convertToMultiNamespaceTypeVersion: version, name }) => { + const types = acc.get(version!) ?? new Set(); + return acc.set(version!, types.add(name)); + }, new Map>()); + + return Array.from(transformMap, ([version, types]) => ({ + version, + transform: (doc) => { + const { namespace, references } = doc; + if (namespace && references?.length) { + return { + transformedDoc: { + ...doc, + references: references.map(({ type, id, ...attrs }) => ({ + ...attrs, + type, + id: types.has(type) ? deterministicallyRegenerateObjectId(namespace, type, id) : id, + })), + }, + additionalDocs: [], + }; + } + return { transformedDoc: doc, additionalDocs: [] }; + }, + transformType: 'reference', + })); +} + +/** + * Transforms are sorted in ascending order by version. One version may contain multiple transforms; 'reference' transforms always run + * first, 'convert' transforms always run second, and 'migrate' transforms always run last. This is because: + * 1. 'convert' transforms get rid of the `namespace` field, which must be present for 'reference' transforms to function correctly. + * 2. 'migrate' transforms are defined by the consumer, and may change the object type or migrationVersion which resets the migration loop + * and could cause any remaining transforms for this version to be skipped. + */ +function transformComparator(a: Transform, b: Transform) { + const semver = Semver.compare(a.version, b.version); + if (semver !== 0) { + return semver; + } else if (a.transformType !== b.transformType) { + if (a.transformType === 'migrate') { + return 1; + } else if (b.transformType === 'migrate') { + return -1; + } else if (a.transformType === 'convert') { + return 1; + } else if (b.transformType === 'convert') { + return -1; + } + } + return 0; +} + /** * If a specific transform function fails, this tacks on a bit of information * about the document and transform that caused the failure. @@ -342,7 +670,7 @@ function wrapWithTry( throw new Error(`Invalid saved object returned from migration ${type}:${version}.`); } - return result; + return { transformedDoc: result, additionalDocs: [] }; } catch (error) { const failedTransform = `${type}:${version}`; const failedDoc = JSON.stringify(doc); @@ -354,32 +682,52 @@ function wrapWithTry( }; } +/** + * Determines whether or not a document has any pending transforms that should be applied based on its coreMigrationVersion field. + * Currently, only reference transforms qualify. + */ +function getHasPendingCoreMigrationVersionTransform( + doc: SavedObjectUnsanitizedDoc, + migrations: ActiveMigrations, + prop: string +) { + if (!migrations.hasOwnProperty(prop)) { + return false; + } + + const { latestCoreMigrationVersion } = migrations[prop]; + const { coreMigrationVersion } = doc; + return ( + latestCoreMigrationVersion && + (!coreMigrationVersion || Semver.gt(latestCoreMigrationVersion, coreMigrationVersion)) + ); +} + /** * Finds the first unmigrated property in the specified document. */ function nextUnmigratedProp(doc: SavedObjectUnsanitizedDoc, migrations: ActiveMigrations) { return props(doc).find((p) => { - const latestVersion = propVersion(migrations, p); + const latestMigrationVersion = propVersion(migrations, p); const docVersion = propVersion(doc, p); - if (latestVersion === docVersion) { - return false; - } - // We verify that the version is not greater than the version supported by Kibana. // If we didn't, this would cause an infinite loop, as we'd be unable to migrate the property // but it would continue to show up as unmigrated. - // If we have a docVersion and the latestVersion is smaller than it or does not exist, + // If we have a docVersion and the latestMigrationVersion is smaller than it or does not exist, // we are dealing with a document that belongs to a future Kibana / plugin version. - if (docVersion && (!latestVersion || Semver.gt(docVersion, latestVersion))) { + if (docVersion && (!latestMigrationVersion || Semver.gt(docVersion, latestMigrationVersion))) { throw Boom.badData( `Document "${doc.id}" has property "${p}" which belongs to a more recent` + - ` version of Kibana [${docVersion}]. The last known version is [${latestVersion}]`, + ` version of Kibana [${docVersion}]. The last known version is [${latestMigrationVersion}]`, doc ); } - return true; + return ( + (latestMigrationVersion && latestMigrationVersion !== docVersion) || + getHasPendingCoreMigrationVersionTransform(doc, migrations, p) // If the object itself is up-to-date, check if its references are up-to-date too + ); }); } @@ -389,23 +737,42 @@ function nextUnmigratedProp(doc: SavedObjectUnsanitizedDoc, migrations: ActiveMi function migrateProp( doc: SavedObjectUnsanitizedDoc, prop: string, - migrations: ActiveMigrations -): SavedObjectUnsanitizedDoc { + migrations: ActiveMigrations, + convertNamespaceTypes: boolean +): TransformResult { const originalType = doc.type; let migrationVersion = _.clone(doc.migrationVersion) || {}; - const typeChanged = () => !doc.hasOwnProperty(prop) || doc.type !== originalType; + let additionalDocs: SavedObjectUnsanitizedDoc[] = []; - for (const { version, transform } of applicableTransforms(migrations, doc, prop)) { - doc = transform(doc); - migrationVersion = updateMigrationVersion(doc, migrationVersion, prop, version); - doc.migrationVersion = _.clone(migrationVersion); + for (const { version, transform, transformType } of applicableTransforms(migrations, doc, prop)) { + const currentVersion = propVersion(doc, prop); + if (currentVersion && Semver.gt(currentVersion, version)) { + // the previous transform function increased the object's migrationVersion; break out of the loop + break; + } - if (typeChanged()) { + if (convertNamespaceTypes || (transformType !== 'convert' && transformType !== 'reference')) { + // migrate transforms are always applied, but conversion transforms and reference transforms are only applied during index migrations + const result = transform(doc); + doc = result.transformedDoc; + additionalDocs = [...additionalDocs, ...result.additionalDocs]; + } + if (transformType === 'reference') { + // regardless of whether or not the reference transform was applied, update the object's coreMigrationVersion + // this is needed to ensure that we don't have an endless migration loop + doc.coreMigrationVersion = version; + } else { + migrationVersion = updateMigrationVersion(doc, migrationVersion, prop, version); + doc.migrationVersion = _.clone(migrationVersion); + } + + if (doc.type !== originalType) { + // the transform function changed the object's type; break out of the loop break; } } - return doc; + return { transformedDoc: doc, additionalDocs }; } /** @@ -417,9 +784,14 @@ function applicableTransforms( prop: string ) { const minVersion = propVersion(doc, prop); + const minReferenceVersion = doc.coreMigrationVersion || '0.0.0'; const { transforms } = migrations[prop]; return minVersion - ? transforms.filter(({ version }) => Semver.gt(version, minVersion)) + ? transforms.filter(({ version, transformType }) => + transformType === 'reference' + ? Semver.gt(version, minReferenceVersion) + : Semver.gt(version, minVersion) + ) : transforms; } @@ -466,3 +838,14 @@ function assertNoDowngrades( ); } } + +/** + * Deterministically regenerates a saved object's ID based upon it's current namespace, type, and ID. This ensures that we can regenerate + * any existing object IDs without worrying about collisions if two objects that exist in different namespaces share an ID. It also ensures + * that we can later regenerate any inbound object references to match. + * + * @note This is only intended to be used when single-namespace object types are converted into multi-namespace object types. + */ +function deterministicallyRegenerateObjectId(namespace: string, type: string, id: string) { + return uuidv5(`${namespace}:${type}:${id}`, uuidv5.DNS); // the uuidv5 namespace constant (uuidv5.DNS) is arbitrary +} diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts index 025730e71b923..32ecea94826ff 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts @@ -557,6 +557,7 @@ describe('ElasticIndex', () => { mappings, count, migrations, + kibanaVersion, }: any) { client.indices.get = jest.fn().mockReturnValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise({ @@ -570,7 +571,12 @@ describe('ElasticIndex', () => { }) ); - const hasMigrations = await Index.migrationsUpToDate(client, index, migrations); + const hasMigrations = await Index.migrationsUpToDate( + client, + index, + migrations, + kibanaVersion + ); return { hasMigrations }; } @@ -584,6 +590,7 @@ describe('ElasticIndex', () => { }, count: 0, migrations: { dashy: '2.3.4' }, + kibanaVersion: '7.10.0', }); expect(hasMigrations).toBeFalsy(); @@ -611,6 +618,7 @@ describe('ElasticIndex', () => { }, count: 2, migrations: {}, + kibanaVersion: '7.10.0', }); expect(hasMigrations).toBeTruthy(); @@ -652,6 +660,7 @@ describe('ElasticIndex', () => { }, count: 3, migrations: { dashy: '23.2.5' }, + kibanaVersion: '7.10.0', }); expect(hasMigrations).toBeFalsy(); @@ -677,6 +686,7 @@ describe('ElasticIndex', () => { bashy: '99.9.3', flashy: '3.4.5', }, + kibanaVersion: '7.10.0', }); function shouldClause(type: string, version: string) { @@ -702,6 +712,15 @@ describe('ElasticIndex', () => { shouldClause('dashy', '23.2.5'), shouldClause('bashy', '99.9.3'), shouldClause('flashy', '3.4.5'), + { + bool: { + must_not: { + term: { + coreMigrationVersion: '7.10.0', + }, + }, + }, + }, ], }, }, diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.ts b/src/core/server/saved_objects/migrations/core/elastic_index.ts index c6c00a123295d..9cdec926a56ba 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.ts @@ -147,6 +147,7 @@ export async function migrationsUpToDate( client: MigrationEsClient, index: string, migrationVersion: SavedObjectsMigrationVersion, + kibanaVersion: string, retryCount: number = 10 ): Promise { try { @@ -165,18 +166,29 @@ export async function migrationsUpToDate( body: { query: { bool: { - should: Object.entries(migrationVersion).map(([type, latestVersion]) => ({ - bool: { - must: [ - { exists: { field: type } }, - { - bool: { - must_not: { term: { [`migrationVersion.${type}`]: latestVersion } }, + should: [ + ...Object.entries(migrationVersion).map(([type, latestVersion]) => ({ + bool: { + must: [ + { exists: { field: type } }, + { + bool: { + must_not: { term: { [`migrationVersion.${type}`]: latestVersion } }, + }, + }, + ], + }, + })), + { + bool: { + must_not: { + term: { + coreMigrationVersion: kibanaVersion, }, }, - ], + }, }, - })), + ], }, }, }, @@ -194,7 +206,7 @@ export async function migrationsUpToDate( await new Promise((r) => setTimeout(r, 1000)); - return await migrationsUpToDate(client, index, migrationVersion, retryCount - 1); + return await migrationsUpToDate(client, index, migrationVersion, kibanaVersion, retryCount - 1); } } diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts index e82e30ef30031..a8abc75114a96 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts @@ -24,6 +24,7 @@ describe('IndexMigrator', () => { batchSize: 10, client: elasticsearchClientMock.createElasticsearchClient(), index: '.kibana', + kibanaVersion: '7.10.0', log: loggingSystemMock.create().get(), mappingProperties: {}, pollInterval: 1, @@ -31,6 +32,7 @@ describe('IndexMigrator', () => { documentMigrator: { migrationVersion: {}, migrate: _.identity, + migrateAndConvert: _.identity, prepareMigrations: jest.fn(), }, serializer: new SavedObjectsSerializer(new SavedObjectTypeRegistry()), @@ -58,6 +60,7 @@ describe('IndexMigrator', () => { namespaces: '2f4316de49999235636386fe51dc06c1', originId: '2f4316de49999235636386fe51dc06c1', references: '7997cf5a56cc02bdc9c93361bde732b0', + coreMigrationVersion: '2f4316de49999235636386fe51dc06c1', type: '2f4316de49999235636386fe51dc06c1', updated_at: '00da57df13e94e9d98437d13ace4bfe0', }, @@ -78,6 +81,7 @@ describe('IndexMigrator', () => { id: { type: 'keyword' }, }, }, + coreMigrationVersion: { type: 'keyword' }, }, }, settings: { number_of_shards: 1, auto_expand_replicas: '0-1' }, @@ -179,6 +183,7 @@ describe('IndexMigrator', () => { namespaces: '2f4316de49999235636386fe51dc06c1', originId: '2f4316de49999235636386fe51dc06c1', references: '7997cf5a56cc02bdc9c93361bde732b0', + coreMigrationVersion: '2f4316de49999235636386fe51dc06c1', type: '2f4316de49999235636386fe51dc06c1', updated_at: '00da57df13e94e9d98437d13ace4bfe0', }, @@ -200,6 +205,7 @@ describe('IndexMigrator', () => { id: { type: 'keyword' }, }, }, + coreMigrationVersion: { type: 'keyword' }, }, }, settings: { number_of_shards: 1, auto_expand_replicas: '0-1' }, @@ -240,6 +246,7 @@ describe('IndexMigrator', () => { namespaces: '2f4316de49999235636386fe51dc06c1', originId: '2f4316de49999235636386fe51dc06c1', references: '7997cf5a56cc02bdc9c93361bde732b0', + coreMigrationVersion: '2f4316de49999235636386fe51dc06c1', type: '2f4316de49999235636386fe51dc06c1', updated_at: '00da57df13e94e9d98437d13ace4bfe0', }, @@ -261,6 +268,7 @@ describe('IndexMigrator', () => { id: { type: 'keyword' }, }, }, + coreMigrationVersion: { type: 'keyword' }, }, }, settings: { number_of_shards: 1, auto_expand_replicas: '0-1' }, @@ -307,17 +315,15 @@ describe('IndexMigrator', () => { test('transforms all docs from the original index', async () => { let count = 0; const { client } = testOpts; - const migrateDoc = jest.fn((doc: SavedObjectUnsanitizedDoc) => { - return { - ...doc, - attributes: { name: ++count }, - }; + const migrateAndConvertDoc = jest.fn((doc: SavedObjectUnsanitizedDoc) => { + return [{ ...doc, attributes: { name: ++count } }]; }); testOpts.documentMigrator = { migrationVersion: { foo: '1.2.3' }, + migrate: jest.fn(), + migrateAndConvert: migrateAndConvertDoc, prepareMigrations: jest.fn(), - migrate: migrateDoc, }; withIndex(client, { @@ -331,14 +337,14 @@ describe('IndexMigrator', () => { await new IndexMigrator(testOpts).migrate(); expect(count).toEqual(2); - expect(migrateDoc).toHaveBeenCalledWith({ + expect(migrateAndConvertDoc).toHaveBeenNthCalledWith(1, { id: '1', type: 'foo', attributes: { name: 'Bar' }, migrationVersion: {}, references: [], }); - expect(migrateDoc).toHaveBeenCalledWith({ + expect(migrateAndConvertDoc).toHaveBeenNthCalledWith(2, { id: '2', type: 'foo', attributes: { name: 'Baz' }, @@ -363,14 +369,15 @@ describe('IndexMigrator', () => { test('rejects when the migration function throws an error', async () => { const { client } = testOpts; - const migrateDoc = jest.fn((doc: SavedObjectUnsanitizedDoc) => { + const migrateAndConvertDoc = jest.fn((doc: SavedObjectUnsanitizedDoc) => { throw new Error('error migrating document'); }); testOpts.documentMigrator = { migrationVersion: { foo: '1.2.3' }, + migrate: jest.fn(), + migrateAndConvert: migrateAndConvertDoc, prepareMigrations: jest.fn(), - migrate: migrateDoc, }; withIndex(client, { diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.ts b/src/core/server/saved_objects/migrations/core/index_migrator.ts index bb341e6173aea..869729daab4f3 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.ts @@ -60,13 +60,14 @@ export class IndexMigrator { * Determines what action the migration system needs to take (none, patch, migrate). */ async function requiresMigration(context: Context): Promise { - const { client, alias, documentMigrator, dest, log } = context; + const { client, alias, documentMigrator, dest, kibanaVersion, log } = context; // Have all of our known migrations been run against the index? const hasMigrations = await Index.migrationsUpToDate( client, alias, - documentMigrator.migrationVersion + documentMigrator.migrationVersion, + kibanaVersion ); if (!hasMigrations) { @@ -184,7 +185,7 @@ async function migrateSourceToDest(context: Context) { await Index.write( client, dest.indexName, - await migrateRawDocs(serializer, documentMigrator.migrate, docs, log) + await migrateRawDocs(serializer, documentMigrator.migrateAndConvert, docs, log) ); } } diff --git a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts index 4e6c2d0ddfd5c..f3e4b67876b71 100644 --- a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts +++ b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts @@ -15,7 +15,9 @@ import { createSavedObjectsMigrationLoggerMock } from '../../migrations/mocks'; describe('migrateRawDocs', () => { test('converts raw docs to saved objects', async () => { - const transform = jest.fn((doc: any) => set(doc, 'attributes.name', 'HOI!')); + const transform = jest.fn((doc: any) => [ + set(_.cloneDeep(doc), 'attributes.name', 'HOI!'), + ]); const result = await migrateRawDocs( new SavedObjectsSerializer(new SavedObjectTypeRegistry()), transform, @@ -37,14 +39,30 @@ describe('migrateRawDocs', () => { }, ]); - expect(transform).toHaveBeenCalled(); + const obj1 = { + id: 'b', + type: 'a', + attributes: { name: 'AAA' }, + migrationVersion: {}, + references: [], + }; + const obj2 = { + id: 'd', + type: 'c', + attributes: { name: 'DDD' }, + migrationVersion: {}, + references: [], + }; + expect(transform).toHaveBeenCalledTimes(2); + expect(transform).toHaveBeenNthCalledWith(1, obj1); + expect(transform).toHaveBeenNthCalledWith(2, obj2); }); test('passes invalid docs through untouched and logs error', async () => { const logger = createSavedObjectsMigrationLoggerMock(); - const transform = jest.fn((doc: any) => - set(_.cloneDeep(doc), 'attributes.name', 'TADA') - ); + const transform = jest.fn((doc: any) => [ + set(_.cloneDeep(doc), 'attributes.name', 'TADA'), + ]); const result = await migrateRawDocs( new SavedObjectsSerializer(new SavedObjectTypeRegistry()), transform, @@ -63,23 +81,53 @@ describe('migrateRawDocs', () => { }, ]); - expect(transform.mock.calls).toEqual([ - [ - { - id: 'd', - type: 'c', - attributes: { - name: 'DDD', - }, - migrationVersion: {}, - references: [], - }, - ], - ]); + const obj2 = { + id: 'd', + type: 'c', + attributes: { name: 'DDD' }, + migrationVersion: {}, + references: [], + }; + expect(transform).toHaveBeenCalledTimes(1); + expect(transform).toHaveBeenCalledWith(obj2); expect(logger.error).toBeCalledTimes(1); }); + test('handles when one document is transformed into multiple documents', async () => { + const transform = jest.fn((doc: any) => [ + set(_.cloneDeep(doc), 'attributes.name', 'HOI!'), + { id: 'bar', type: 'foo', attributes: { name: 'baz' } }, + ]); + const result = await migrateRawDocs( + new SavedObjectsSerializer(new SavedObjectTypeRegistry()), + transform, + [{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } }], + createSavedObjectsMigrationLoggerMock() + ); + + expect(result).toEqual([ + { + _id: 'a:b', + _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, + }, + { + _id: 'foo:bar', + _source: { type: 'foo', foo: { name: 'baz' }, references: [] }, + }, + ]); + + const obj = { + id: 'b', + type: 'a', + attributes: { name: 'AAA' }, + migrationVersion: {}, + references: [], + }; + expect(transform).toHaveBeenCalledTimes(1); + expect(transform).toHaveBeenCalledWith(obj); + }); + test('rejects when the transform function throws an error', async () => { const transform = jest.fn((doc: any) => { throw new Error('error during transform'); diff --git a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts index 0f939cd08aff0..fd1b7db36b4eb 100644 --- a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts +++ b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts @@ -15,7 +15,7 @@ import { SavedObjectsSerializer, SavedObjectUnsanitizedDoc, } from '../../serialization'; -import { TransformFn } from './document_migrator'; +import { MigrateAndConvertFn } from './document_migrator'; import { SavedObjectsMigrationLogger } from '.'; /** @@ -28,21 +28,24 @@ import { SavedObjectsMigrationLogger } from '.'; */ export async function migrateRawDocs( serializer: SavedObjectsSerializer, - migrateDoc: TransformFn, + migrateDoc: MigrateAndConvertFn, rawDocs: SavedObjectsRawDoc[], log: SavedObjectsMigrationLogger ): Promise { const migrateDocWithoutBlocking = transformNonBlocking(migrateDoc); const processedDocs = []; for (const raw of rawDocs) { - if (serializer.isRawSavedObject(raw)) { - const savedObject = serializer.rawToSavedObject(raw); + const options = { namespaceTreatment: 'lax' as 'lax' }; + if (serializer.isRawSavedObject(raw, options)) { + const savedObject = serializer.rawToSavedObject(raw, options); savedObject.migrationVersion = savedObject.migrationVersion || {}; processedDocs.push( - serializer.savedObjectToRaw({ - references: [], - ...(await migrateDocWithoutBlocking(savedObject)), - }) + ...(await migrateDocWithoutBlocking(savedObject)).map((attrs) => + serializer.savedObjectToRaw({ + references: [], + ...attrs, + }) + ) ); } else { log.error( @@ -63,8 +66,8 @@ export async function migrateRawDocs( * work in between each transform. */ function transformNonBlocking( - transform: TransformFn -): (doc: SavedObjectUnsanitizedDoc) => Promise { + transform: MigrateAndConvertFn +): (doc: SavedObjectUnsanitizedDoc) => Promise { // promises aren't enough to unblock the event loop return (doc: SavedObjectUnsanitizedDoc) => new Promise((resolve, reject) => { diff --git a/src/core/server/saved_objects/migrations/core/migration_context.ts b/src/core/server/saved_objects/migrations/core/migration_context.ts index a5aaff7df2dd2..62e455c2ddb69 100644 --- a/src/core/server/saved_objects/migrations/core/migration_context.ts +++ b/src/core/server/saved_objects/migrations/core/migration_context.ts @@ -32,6 +32,7 @@ export interface MigrationOpts { scrollDuration: string; client: MigrationEsClient; index: string; + kibanaVersion: string; log: Logger; mappingProperties: SavedObjectsTypeMappingDefinitions; documentMigrator: VersionedTransformer; @@ -54,6 +55,7 @@ export interface Context { source: Index.FullIndexInfo; dest: Index.FullIndexInfo; documentMigrator: VersionedTransformer; + kibanaVersion: string; log: SavedObjectsMigrationLogger; batchSize: number; pollInterval: number; @@ -78,6 +80,7 @@ export async function migrationContext(opts: MigrationOpts): Promise { alias, source, dest, + kibanaVersion: opts.kibanaVersion, log: new MigrationLogger(log), batchSize: opts.batchSize, documentMigrator: opts.documentMigrator, diff --git a/src/core/server/saved_objects/migrations/kibana/__snapshots__/kibana_migrator.test.ts.snap b/src/core/server/saved_objects/migrations/kibana/__snapshots__/kibana_migrator.test.ts.snap index 9311292a6a0ed..32c2536ab0296 100644 --- a/src/core/server/saved_objects/migrations/kibana/__snapshots__/kibana_migrator.test.ts.snap +++ b/src/core/server/saved_objects/migrations/kibana/__snapshots__/kibana_migrator.test.ts.snap @@ -6,6 +6,7 @@ Object { "migrationMappingPropertyHashes": Object { "amap": "510f1f0adb69830cf8a1c5ce2923ed82", "bmap": "510f1f0adb69830cf8a1c5ce2923ed82", + "coreMigrationVersion": "2f4316de49999235636386fe51dc06c1", "migrationVersion": "4a1746014a75ade3a714e1db5763276f", "namespace": "2f4316de49999235636386fe51dc06c1", "namespaces": "2f4316de49999235636386fe51dc06c1", @@ -31,6 +32,9 @@ Object { }, }, }, + "coreMigrationVersion": Object { + "type": "keyword", + }, "migrationVersion": Object { "dynamic": "true", "type": "object", diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index aea29479d2af0..c8bc4b2e14123 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -90,6 +90,7 @@ export class KibanaMigrator { }: KibanaMigratorOptions) { this.client = client; this.kibanaConfig = kibanaConfig; + this.kibanaVersion = kibanaVersion; this.savedObjectsConfig = savedObjectsConfig; this.typeRegistry = typeRegistry; this.serializer = new SavedObjectsSerializer(this.typeRegistry); @@ -177,7 +178,7 @@ export class KibanaMigrator { transformRawDocs: (rawDocs: SavedObjectsRawDoc[]) => migrateRawDocs( this.serializer, - this.documentMigrator.migrate, + this.documentMigrator.migrateAndConvert, rawDocs, new MigrationLogger(this.log) ), @@ -192,6 +193,7 @@ export class KibanaMigrator { client: createMigrationEsClient(this.client, this.log, this.migrationsRetryDelay), documentMigrator: this.documentMigrator, index, + kibanaVersion: this.kibanaVersion, log: this.log, mappingProperties: indexMap[index].typeMappings, pollInterval: this.savedObjectsConfig.pollInterval, diff --git a/src/core/server/saved_objects/migrations/types.ts b/src/core/server/saved_objects/migrations/types.ts index b54d0222a1478..52b4f50d599d9 100644 --- a/src/core/server/saved_objects/migrations/types.ts +++ b/src/core/server/saved_objects/migrations/types.ts @@ -61,7 +61,7 @@ export interface SavedObjectMigrationContext { /** * A map of {@link SavedObjectMigrationFn | migration functions} to be used for a given type. - * The map's keys must be valid semver versions. + * The map's keys must be valid semver versions, and they cannot exceed the current Kibana version. * * For a given document, only migrations with a higher version number than that of the document will be applied. * Migrations are executed in order, starting from the lowest version and ending with the highest one. diff --git a/src/core/server/saved_objects/object_types/constants.ts b/src/core/server/saved_objects/object_types/constants.ts new file mode 100644 index 0000000000000..4e05c406c653f --- /dev/null +++ b/src/core/server/saved_objects/object_types/constants.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * @internal + */ +export const LEGACY_URL_ALIAS_TYPE = 'legacy-url-alias'; diff --git a/src/core/server/saved_objects/object_types/index.ts b/src/core/server/saved_objects/object_types/index.ts new file mode 100644 index 0000000000000..1a9bccdc17c28 --- /dev/null +++ b/src/core/server/saved_objects/object_types/index.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +export { LEGACY_URL_ALIAS_TYPE } from './constants'; +export { LegacyUrlAlias } from './types'; +export { registerCoreObjectTypes } from './registration'; diff --git a/src/core/server/saved_objects/object_types/registration.test.ts b/src/core/server/saved_objects/object_types/registration.test.ts new file mode 100644 index 0000000000000..9bd7b3d61e099 --- /dev/null +++ b/src/core/server/saved_objects/object_types/registration.test.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { typeRegistryMock } from '../saved_objects_type_registry.mock'; +import { LEGACY_URL_ALIAS_TYPE } from './constants'; +import { registerCoreObjectTypes } from './registration'; + +describe('Core saved object types registration', () => { + describe('#registerCoreObjectTypes', () => { + it('registers all expected types', () => { + const typeRegistry = typeRegistryMock.create(); + registerCoreObjectTypes(typeRegistry); + + expect(typeRegistry.registerType).toHaveBeenCalledTimes(1); + expect(typeRegistry.registerType).toHaveBeenCalledWith( + expect.objectContaining({ name: LEGACY_URL_ALIAS_TYPE }) + ); + }); + }); +}); diff --git a/src/core/server/saved_objects/object_types/registration.ts b/src/core/server/saved_objects/object_types/registration.ts new file mode 100644 index 0000000000000..82562ac53a109 --- /dev/null +++ b/src/core/server/saved_objects/object_types/registration.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { LEGACY_URL_ALIAS_TYPE } from './constants'; +import { ISavedObjectTypeRegistry, SavedObjectsType, SavedObjectTypeRegistry } from '..'; + +const legacyUrlAliasType: SavedObjectsType = { + name: LEGACY_URL_ALIAS_TYPE, + namespaceType: 'agnostic', + mappings: { + dynamic: false, // we aren't querying or aggregating over this data, so we don't need to specify any fields + properties: {}, + }, + hidden: true, +}; + +/** + * @internal + */ +export function registerCoreObjectTypes( + typeRegistry: ISavedObjectTypeRegistry & Pick +) { + typeRegistry.registerType(legacyUrlAliasType); +} diff --git a/src/core/server/saved_objects/object_types/types.ts b/src/core/server/saved_objects/object_types/types.ts new file mode 100644 index 0000000000000..8391311cbefdf --- /dev/null +++ b/src/core/server/saved_objects/object_types/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * @internal + */ +export interface LegacyUrlAlias { + targetNamespace: string; + targetType: string; + targetId: string; + lastResolved?: string; + resolveCounter?: number; + disabled?: boolean; +} diff --git a/src/core/server/saved_objects/routes/bulk_create.ts b/src/core/server/saved_objects/routes/bulk_create.ts index eee9b9b1a0bff..6d57eaa3777e6 100644 --- a/src/core/server/saved_objects/routes/bulk_create.ts +++ b/src/core/server/saved_objects/routes/bulk_create.ts @@ -29,6 +29,7 @@ export const registerBulkCreateRoute = (router: IRouter, { coreUsageData }: Rout attributes: schema.recordOf(schema.string(), schema.any()), version: schema.maybe(schema.string()), migrationVersion: schema.maybe(schema.recordOf(schema.string(), schema.string())), + coreMigrationVersion: schema.maybe(schema.string()), references: schema.maybe( schema.arrayOf( schema.object({ diff --git a/src/core/server/saved_objects/routes/create.ts b/src/core/server/saved_objects/routes/create.ts index e486580320da9..fd256abac3526 100644 --- a/src/core/server/saved_objects/routes/create.ts +++ b/src/core/server/saved_objects/routes/create.ts @@ -29,6 +29,7 @@ export const registerCreateRoute = (router: IRouter, { coreUsageData }: RouteDep body: schema.object({ attributes: schema.recordOf(schema.string(), schema.any()), migrationVersion: schema.maybe(schema.recordOf(schema.string(), schema.string())), + coreMigrationVersion: schema.maybe(schema.string()), references: schema.maybe( schema.arrayOf( schema.object({ @@ -45,12 +46,25 @@ export const registerCreateRoute = (router: IRouter, { coreUsageData }: RouteDep router.handleLegacyErrors(async (context, req, res) => { const { type, id } = req.params; const { overwrite } = req.query; - const { attributes, migrationVersion, references, initialNamespaces } = req.body; + const { + attributes, + migrationVersion, + coreMigrationVersion, + references, + initialNamespaces, + } = req.body; const usageStatsClient = coreUsageData.getClient(); usageStatsClient.incrementSavedObjectsCreate({ request: req }).catch(() => {}); - const options = { id, overwrite, migrationVersion, references, initialNamespaces }; + const options = { + id, + overwrite, + migrationVersion, + coreMigrationVersion, + references, + initialNamespaces, + }; const result = await context.core.savedObjects.client.create(type, attributes, options); return res.ok({ body: result }); }) diff --git a/src/core/server/saved_objects/routes/index.ts b/src/core/server/saved_objects/routes/index.ts index 67a4e305e87ad..412dd0e7ffbc0 100644 --- a/src/core/server/saved_objects/routes/index.ts +++ b/src/core/server/saved_objects/routes/index.ts @@ -12,6 +12,7 @@ import { Logger } from '../../logging'; import { SavedObjectConfig } from '../saved_objects_config'; import { IKibanaMigrator } from '../migrations'; import { registerGetRoute } from './get'; +import { registerResolveRoute } from './resolve'; import { registerCreateRoute } from './create'; import { registerDeleteRoute } from './delete'; import { registerFindRoute } from './find'; @@ -41,6 +42,7 @@ export function registerRoutes({ const router = http.createRouter('/api/saved_objects/'); registerGetRoute(router, { coreUsageData }); + registerResolveRoute(router, { coreUsageData }); registerCreateRoute(router, { coreUsageData }); registerDeleteRoute(router, { coreUsageData }); registerFindRoute(router, { coreUsageData }); diff --git a/src/core/server/saved_objects/routes/integration_tests/resolve.test.ts b/src/core/server/saved_objects/routes/integration_tests/resolve.test.ts new file mode 100644 index 0000000000000..5ddeb29b8c2d5 --- /dev/null +++ b/src/core/server/saved_objects/routes/integration_tests/resolve.test.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import supertest from 'supertest'; +import { registerResolveRoute } from '../resolve'; +import { ContextService } from '../../../context'; +import { savedObjectsClientMock } from '../../service/saved_objects_client.mock'; +import { CoreUsageStatsClient } from '../../../core_usage_data'; +import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; +import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; +import { HttpService, InternalHttpServiceSetup } from '../../../http'; +import { createHttpServer, createCoreContext } from '../../../http/test_utils'; +import { coreMock } from '../../../mocks'; + +const coreId = Symbol('core'); + +describe('GET /api/saved_objects/resolve/{type}/{id}', () => { + let server: HttpService; + let httpSetup: InternalHttpServiceSetup; + let handlerContext: ReturnType; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + const coreContext = createCoreContext({ coreId }); + server = createHttpServer(coreContext); + + const contextService = new ContextService(coreContext); + httpSetup = await server.setup({ + context: contextService.setup({ pluginDependencies: new Map() }), + }); + + handlerContext = coreMock.createRequestHandlerContext(); + savedObjectsClient = handlerContext.savedObjects.client; + + httpSetup.registerRouteHandlerContext(coreId, 'core', async (ctx, req, res) => { + return handlerContext; + }); + + const router = httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsResolve.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + registerResolveRoute(router, { coreUsageData }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('formats successful response', async () => { + const clientResponse = { + saved_object: { + id: 'logstash-*', + title: 'logstash-*', + type: 'logstash-type', + attributes: {}, + timeFieldName: '@timestamp', + notExpandable: true, + references: [], + }, + outcome: 'exactMatch' as 'exactMatch', + }; + + savedObjectsClient.resolve.mockResolvedValue(clientResponse); + + const result = await supertest(httpSetup.server.listener) + .get('/api/saved_objects/resolve/index-pattern/logstash-*') + .expect(200); + + expect(result.body).toEqual(clientResponse); + }); + + it('calls upon savedObjectClient.resolve', async () => { + await supertest(httpSetup.server.listener) + .get('/api/saved_objects/resolve/index-pattern/logstash-*') + .expect(200); + + expect(savedObjectsClient.resolve).toHaveBeenCalled(); + + const args = savedObjectsClient.resolve.mock.calls[0]; + expect(args).toEqual(['index-pattern', 'logstash-*']); + }); +}); diff --git a/src/core/server/saved_objects/routes/resolve.ts b/src/core/server/saved_objects/routes/resolve.ts new file mode 100644 index 0000000000000..28a3f4b876467 --- /dev/null +++ b/src/core/server/saved_objects/routes/resolve.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; + +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerResolveRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { + router.get( + { + path: '/resolve/{type}/{id}', + validate: { + params: schema.object({ + type: schema.string(), + id: schema.string(), + }), + }, + }, + router.handleLegacyErrors(async (context, req, res) => { + const { type, id } = req.params; + + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsResolve({ request: req }).catch(() => {}); + + const result = await context.core.savedObjects.client.resolve(type, id); + return res.ok({ body: result }); + }) + ); +}; diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index 3c1e217dcc229..4a8caa7686606 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -25,8 +25,10 @@ import { httpServerMock } from '../http/http_server.mocks'; import { SavedObjectsClientFactoryProvider } from './service/lib'; import { NodesVersionCompatibility } from '../elasticsearch/version_check/ensure_es_version'; import { SavedObjectsRepository } from './service/lib/repository'; +import { registerCoreObjectTypes } from './object_types'; jest.mock('./service/lib/repository'); +jest.mock('./object_types'); describe('SavedObjectsService', () => { const createCoreContext = ({ @@ -67,6 +69,16 @@ describe('SavedObjectsService', () => { }); describe('#setup()', () => { + it('calls registerCoreObjectTypes', async () => { + const coreContext = createCoreContext(); + const soService = new SavedObjectsService(coreContext); + + const mockedRegisterCoreObjectTypes = registerCoreObjectTypes as jest.Mock; + expect(mockedRegisterCoreObjectTypes).not.toHaveBeenCalled(); + await soService.setup(createSetupDeps()); + expect(mockedRegisterCoreObjectTypes).toHaveBeenCalledTimes(1); + }); + describe('#setClientFactoryProvider', () => { it('registers the factory to the clientProvider', async () => { const coreContext = createCoreContext(); @@ -130,6 +142,7 @@ describe('SavedObjectsService', () => { describe('#registerType', () => { it('registers the type to the internal typeRegistry', async () => { + // we mocked registerCoreObjectTypes above, so this test case only reflects direct calls to the registerType method const coreContext = createCoreContext(); const soService = new SavedObjectsService(coreContext); const setup = await soService.setup(createSetupDeps()); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 6db4cf4f781b4..40c8c576b0eca 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -43,6 +43,7 @@ import { SavedObjectsImporter, ISavedObjectsImporter } from './import'; import { registerRoutes } from './routes'; import { ServiceStatus } from '../status'; import { calculateStatus$ } from './status'; +import { registerCoreObjectTypes } from './object_types'; /** * Saved Objects is Kibana's data persistence mechanism allowing plugins to @@ -305,6 +306,8 @@ export class SavedObjectsService migratorPromise: this.migrator$.pipe(first()).toPromise(), }); + registerCoreObjectTypes(this.typeRegistry); + return { status$: calculateStatus$( this.migrator$.pipe(switchMap((migrator) => migrator.getStatus$())), diff --git a/src/core/server/saved_objects/serialization/index.ts b/src/core/server/saved_objects/serialization/index.ts index ba6115dbff3ae..3ffaf9cf1e7c8 100644 --- a/src/core/server/saved_objects/serialization/index.ts +++ b/src/core/server/saved_objects/serialization/index.ts @@ -15,6 +15,7 @@ export { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc, SavedObjectsRawDoc, + SavedObjectsRawDocParseOptions, SavedObjectsRawDocSource, } from './types'; export { SavedObjectsSerializer } from './serializer'; diff --git a/src/core/server/saved_objects/serialization/serializer.test.ts b/src/core/server/saved_objects/serialization/serializer.test.ts index 4d0527aee01bc..b09fb1ab30c79 100644 --- a/src/core/server/saved_objects/serialization/serializer.test.ts +++ b/src/core/server/saved_objects/serialization/serializer.test.ts @@ -11,6 +11,7 @@ import { SavedObjectsSerializer } from './serializer'; import { SavedObjectsRawDoc } from './types'; import { typeRegistryMock } from '../saved_objects_type_registry.mock'; import { encodeVersion } from '../version'; +import { LEGACY_URL_ALIAS_TYPE } from '../object_types'; let typeRegistry = typeRegistryMock.create(); typeRegistry.isNamespaceAgnostic.mockReturnValue(true); @@ -131,6 +132,27 @@ describe('#rawToSavedObject', () => { expect(expected).toEqual(actual); }); + test('if specified it copies the _source.coreMigrationVersion property to coreMigrationVersion', () => { + const actual = singleNamespaceSerializer.rawToSavedObject({ + _id: 'foo:bar', + _source: { + type: 'foo', + coreMigrationVersion: '1.2.3', + }, + }); + expect(actual).toHaveProperty('coreMigrationVersion', '1.2.3'); + }); + + test(`if _source.coreMigrationVersion is unspecified it doesn't set coreMigrationVersion`, () => { + const actual = singleNamespaceSerializer.rawToSavedObject({ + _id: 'foo:bar', + _source: { + type: 'foo', + }, + }); + expect(actual).not.toHaveProperty('coreMigrationVersion'); + }); + test(`if version is unspecified it doesn't set version`, () => { const actual = singleNamespaceSerializer.rawToSavedObject({ _id: 'foo:bar', @@ -288,6 +310,7 @@ describe('#rawToSavedObject', () => { foo: '1.2.3', bar: '9.8.7', }, + coreMigrationVersion: '4.5.6', namespace: 'foo-namespace', updated_at: String(new Date()), references: [], @@ -412,6 +435,41 @@ describe('#rawToSavedObject', () => { test(`doesn't copy _source.namespace to namespace`, () => { expect(actual).not.toHaveProperty('namespace'); }); + + describe('with lax namespaceTreatment', () => { + const options = { namespaceTreatment: 'lax' as 'lax' }; + + test(`removes type prefix from _id and, and does not copy _source.namespace to namespace`, () => { + const _actual = multiNamespaceSerializer.rawToSavedObject(raw, options); + expect(_actual).toHaveProperty('id', 'bar'); + expect(_actual).not.toHaveProperty('namespace'); + }); + + test(`removes type and namespace prefix from _id, and copies _source.namespace to namespace`, () => { + const _id = `${raw._source.namespace}:${raw._id}`; + const _actual = multiNamespaceSerializer.rawToSavedObject({ ...raw, _id }, options); + expect(_actual).toHaveProperty('id', 'bar'); + expect(_actual).toHaveProperty('namespace', raw._source.namespace); // "baz" + }); + + test(`removes type and namespace prefix from _id when the namespace matches the type`, () => { + const _raw = createSampleDoc({ _id: 'foo:foo:bar', _source: { namespace: 'foo' } }); + const _actual = multiNamespaceSerializer.rawToSavedObject(_raw, options); + expect(_actual).toHaveProperty('id', 'bar'); + expect(_actual).toHaveProperty('namespace', 'foo'); + }); + + test(`does not remove the entire _id when the namespace matches the type`, () => { + // This is not a realistic/valid document, but we defensively check to ensure we aren't trimming the entire ID. + // In this test case, a multi-namespace document has a raw ID with the type prefix "foo:" and an object ID of "foo:" (no namespace + // prefix). This document *also* has a `namespace` field the same as the type, while it should not have a `namespace` field at all + // since it has no namespace prefix in its raw ID. + const _raw = createSampleDoc({ _id: 'foo:foo:', _source: { namespace: 'foo' } }); + const _actual = multiNamespaceSerializer.rawToSavedObject(_raw, options); + expect(_actual).toHaveProperty('id', 'foo:'); + expect(_actual).not.toHaveProperty('namespace'); + }); + }); }); describe('multi-namespace type with namespaces', () => { @@ -515,6 +573,25 @@ describe('#savedObjectToRaw', () => { expect(actual._source).not.toHaveProperty('migrationVersion'); }); + test('it copies the coreMigrationVersion property to _source.coreMigrationVersion', () => { + const actual = singleNamespaceSerializer.savedObjectToRaw({ + type: '', + attributes: {}, + coreMigrationVersion: '1.2.3', + } as any); + + expect(actual._source).toHaveProperty('coreMigrationVersion', '1.2.3'); + }); + + test(`if unspecified it doesn't add coreMigrationVersion property to _source`, () => { + const actual = singleNamespaceSerializer.savedObjectToRaw({ + type: '', + attributes: {}, + } as any); + + expect(actual._source).not.toHaveProperty('coreMigrationVersion'); + }); + test('it decodes the version property to _seq_no and _primary_term', () => { const actual = singleNamespaceSerializer.savedObjectToRaw({ type: '', @@ -841,6 +918,116 @@ describe('#isRawSavedObject', () => { }); }); + describe('multi-namespace type with a namespace', () => { + test('is true if the id is prefixed with type and the type matches', () => { + expect( + multiNamespaceSerializer.isRawSavedObject({ + _id: 'hello:world', + _source: { + type: 'hello', + hello: {}, + namespace: 'foo', + }, + }) + ).toBeTruthy(); + }); + + test('is false if the id is not prefixed', () => { + expect( + multiNamespaceSerializer.isRawSavedObject({ + _id: 'world', + _source: { + type: 'hello', + hello: {}, + namespace: 'foo', + }, + }) + ).toBeFalsy(); + }); + + test('is false if the id is prefixed with type and namespace', () => { + expect( + multiNamespaceSerializer.isRawSavedObject({ + _id: 'foo:hello:world', + _source: { + type: 'hello', + hello: {}, + namespace: 'foo', + }, + }) + ).toBeFalsy(); + }); + + test('is true if the id is prefixed with type and namespace, and namespaceTreatment is lax', () => { + const options = { namespaceTreatment: 'lax' as 'lax' }; + expect( + multiNamespaceSerializer.isRawSavedObject( + { + _id: 'foo:hello:world', + _source: { + type: 'hello', + hello: {}, + namespace: 'foo', + }, + }, + options + ) + ).toBeTruthy(); + }); + + test(`is false if the type prefix omits the :`, () => { + expect( + namespaceAgnosticSerializer.isRawSavedObject({ + _id: 'helloworld', + _source: { + type: 'hello', + hello: {}, + namespace: 'foo', + }, + }) + ).toBeFalsy(); + }); + + test('is false if the type attribute is missing', () => { + expect( + multiNamespaceSerializer.isRawSavedObject({ + _id: 'hello:world', + _source: { + hello: {}, + namespace: 'foo', + } as any, + }) + ).toBeFalsy(); + }); + + test('is false if the type attribute does not match the id', () => { + expect( + multiNamespaceSerializer.isRawSavedObject({ + _id: 'hello:world', + _source: { + type: 'jam', + jam: {}, + hello: {}, + namespace: 'foo', + }, + }) + ).toBeFalsy(); + }); + + test('is false if there is no [type] attribute', () => { + expect( + multiNamespaceSerializer.isRawSavedObject({ + _id: 'hello:world', + _source: { + type: 'hello', + jam: {}, + namespace: 'foo', + }, + }) + ).toBeFalsy(); + }); + }); + describe('namespace-agnostic type with a namespace', () => { test('is true if the id is prefixed with type and the type matches', () => { expect( @@ -950,6 +1137,13 @@ describe('#generateRawId', () => { }); }); + describe('multi-namespace type with a namespace', () => { + test(`uses the id that is specified and doesn't prefix the namespace`, () => { + const id = multiNamespaceSerializer.generateRawId('foo', 'hello', 'world'); + expect(id).toEqual('hello:world'); + }); + }); + describe('namespace-agnostic type with a namespace', () => { test(`uses the id that is specified and doesn't prefix the namespace`, () => { const id = namespaceAgnosticSerializer.generateRawId('foo', 'hello', 'world'); @@ -957,3 +1151,24 @@ describe('#generateRawId', () => { }); }); }); + +describe('#generateRawLegacyUrlAliasId', () => { + describe(`returns expected value`, () => { + const expected = `${LEGACY_URL_ALIAS_TYPE}:foo:bar:baz`; + + test(`for single-namespace types`, () => { + const id = singleNamespaceSerializer.generateRawLegacyUrlAliasId('foo', 'bar', 'baz'); + expect(id).toEqual(expected); + }); + + test(`for multi-namespace types`, () => { + const id = multiNamespaceSerializer.generateRawLegacyUrlAliasId('foo', 'bar', 'baz'); + expect(id).toEqual(expected); + }); + + test(`for namespace-agnostic types`, () => { + const id = namespaceAgnosticSerializer.generateRawLegacyUrlAliasId('foo', 'bar', 'baz'); + expect(id).toEqual(expected); + }); + }); +}); diff --git a/src/core/server/saved_objects/serialization/serializer.ts b/src/core/server/saved_objects/serialization/serializer.ts index 7a1de0ed2c960..4e9c3b6be03cf 100644 --- a/src/core/server/saved_objects/serialization/serializer.ts +++ b/src/core/server/saved_objects/serialization/serializer.ts @@ -6,9 +6,14 @@ * Public License, v 1. */ +import { LEGACY_URL_ALIAS_TYPE } from '../object_types'; import { decodeVersion, encodeVersion } from '../version'; import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry'; -import { SavedObjectsRawDoc, SavedObjectSanitizedDoc } from './types'; +import { + SavedObjectsRawDoc, + SavedObjectSanitizedDoc, + SavedObjectsRawDocParseOptions, +} from './types'; /** * A serializer that can be used to manually convert {@link SavedObjectsRawDoc | raw} or @@ -30,42 +35,60 @@ export class SavedObjectsSerializer { /** * Determines whether or not the raw document can be converted to a saved object. * - * @param {SavedObjectsRawDoc} rawDoc - The raw ES document to be tested + * @param {SavedObjectsRawDoc} doc - The raw ES document to be tested + * @param {SavedObjectsRawDocParseOptions} options - Options for parsing the raw document. */ - public isRawSavedObject(rawDoc: SavedObjectsRawDoc) { - const { type, namespace } = rawDoc._source; - const namespacePrefix = - namespace && this.registry.isSingleNamespace(type) ? `${namespace}:` : ''; - return Boolean( - type && - rawDoc._id.startsWith(`${namespacePrefix}${type}:`) && - rawDoc._source.hasOwnProperty(type) - ); + public isRawSavedObject(doc: SavedObjectsRawDoc, options: SavedObjectsRawDocParseOptions = {}) { + const { namespaceTreatment = 'strict' } = options; + const { _id, _source } = doc; + const { type, namespace } = _source; + if (!type) { + return false; + } + const { idMatchesPrefix } = this.parseIdPrefix(namespace, type, _id, namespaceTreatment); + return idMatchesPrefix && _source.hasOwnProperty(type); } /** * Converts a document from the format that is stored in elasticsearch to the saved object client format. * - * @param {SavedObjectsRawDoc} doc - The raw ES document to be converted to saved object format. + * @param {SavedObjectsRawDoc} doc - The raw ES document to be converted to saved object format. + * @param {SavedObjectsRawDocParseOptions} options - Options for parsing the raw document. */ - public rawToSavedObject(doc: SavedObjectsRawDoc): SavedObjectSanitizedDoc { + public rawToSavedObject( + doc: SavedObjectsRawDoc, + options: SavedObjectsRawDocParseOptions = {} + ): SavedObjectSanitizedDoc { + const { namespaceTreatment = 'strict' } = options; const { _id, _source, _seq_no, _primary_term } = doc; - const { type, namespace, namespaces, originId } = _source; + const { + type, + namespaces, + originId, + migrationVersion, + references, + coreMigrationVersion, + } = _source; const version = _seq_no != null || _primary_term != null ? encodeVersion(_seq_no!, _primary_term!) : undefined; + const { id, namespace } = this.trimIdPrefix(_source.namespace, type, _id, namespaceTreatment); + const includeNamespace = + namespace && (namespaceTreatment === 'lax' || this.registry.isSingleNamespace(type)); + const includeNamespaces = this.registry.isMultiNamespace(type); return { type, - id: this.trimIdPrefix(namespace, type, _id), - ...(namespace && this.registry.isSingleNamespace(type) && { namespace }), - ...(namespaces && this.registry.isMultiNamespace(type) && { namespaces }), + id, + ...(includeNamespace && { namespace }), + ...(includeNamespaces && { namespaces }), ...(originId && { originId }), attributes: _source[type], - references: _source.references || [], - ...(_source.migrationVersion && { migrationVersion: _source.migrationVersion }), + references: references || [], + ...(migrationVersion && { migrationVersion }), + ...(coreMigrationVersion && { coreMigrationVersion }), ...(_source.updated_at && { updated_at: _source.updated_at }), ...(version && { version }), }; @@ -89,6 +112,7 @@ export class SavedObjectsSerializer { updated_at, version, references, + coreMigrationVersion, } = savedObj; const source = { [type]: attributes, @@ -98,6 +122,7 @@ export class SavedObjectsSerializer { ...(namespaces && this.registry.isMultiNamespace(type) && { namespaces }), ...(originId && { originId }), ...(migrationVersion && { migrationVersion }), + ...(coreMigrationVersion && { coreMigrationVersion }), ...(updated_at && { updated_at }), }; @@ -121,22 +146,77 @@ export class SavedObjectsSerializer { return `${namespacePrefix}${type}:${id}`; } - private trimIdPrefix(namespace: string | undefined, type: string, id: string) { + /** + * Given a saved object type and id, generates the compound id that is stored in the raw document for its legacy URL alias. + * + * @param {string} namespace - The namespace of the saved object + * @param {string} type - The saved object type + * @param {string} id - The id of the saved object + */ + public generateRawLegacyUrlAliasId(namespace: string, type: string, id: string) { + return `${LEGACY_URL_ALIAS_TYPE}:${namespace}:${type}:${id}`; + } + + /** + * Given a document's source namespace, type, and raw ID, trim the ID prefix (based on the namespaceType), returning the object ID and the + * detected namespace. A single-namespace object is only considered to exist in a namespace if its raw ID is prefixed by that *and* it has + * the namespace field in its source. + */ + private trimIdPrefix( + sourceNamespace: string | undefined, + type: string, + id: string, + namespaceTreatment: 'strict' | 'lax' + ) { assertNonEmptyString(id, 'document id'); assertNonEmptyString(type, 'saved object type'); - const namespacePrefix = - namespace && this.registry.isSingleNamespace(type) ? `${namespace}:` : ''; - const prefix = `${namespacePrefix}${type}:`; + const { prefix, idMatchesPrefix, namespace } = this.parseIdPrefix( + sourceNamespace, + type, + id, + namespaceTreatment + ); + return { + id: idMatchesPrefix ? id.slice(prefix.length) : id, + namespace, + }; + } - if (!id.startsWith(prefix)) { - return id; + private parseIdPrefix( + sourceNamespace: string | undefined, + type: string, + id: string, + namespaceTreatment: 'strict' | 'lax' + ) { + let prefix: string; // the prefix that is used to validate this raw object ID + let namespace: string | undefined; // the namespace that is in the raw object ID (only for single-namespace objects) + const parseFlexibly = namespaceTreatment === 'lax' && this.registry.isMultiNamespace(type); + if (sourceNamespace && (this.registry.isSingleNamespace(type) || parseFlexibly)) { + prefix = `${sourceNamespace}:${type}:`; + if (parseFlexibly && !checkIdMatchesPrefix(id, prefix)) { + prefix = `${type}:`; + } else { + // this is either a single-namespace object, or is being converted into a multi-namespace object + namespace = sourceNamespace; + } + } else { + // there is no source namespace, OR there is a source namespace but this is not a single-namespace object + prefix = `${type}:`; } - return id.slice(prefix.length); + return { + prefix, + idMatchesPrefix: checkIdMatchesPrefix(id, prefix), + namespace, + }; } } +function checkIdMatchesPrefix(id: string, prefix: string) { + return id.startsWith(prefix) && id.length > prefix.length; +} + function assertNonEmptyString(value: string, name: string) { if (!value || typeof value !== 'string') { throw new TypeError(`Expected "${value}" to be a ${name}`); diff --git a/src/core/server/saved_objects/serialization/types.ts b/src/core/server/saved_objects/serialization/types.ts index 95deedbb7d9c0..5de168a08f1db 100644 --- a/src/core/server/saved_objects/serialization/types.ts +++ b/src/core/server/saved_objects/serialization/types.ts @@ -43,6 +43,7 @@ interface SavedObjectDoc { namespace?: string; namespaces?: string[]; migrationVersion?: SavedObjectsMigrationVersion; + coreMigrationVersion?: string; version?: string; updated_at?: string; originId?: string; @@ -68,3 +69,19 @@ export type SavedObjectUnsanitizedDoc = SavedObjectDoc & Partial * @public */ export type SavedObjectSanitizedDoc = SavedObjectDoc & Referencable; + +/** + * Options that can be specified when using the saved objects serializer to parse a raw document. + * + * @public + */ +export interface SavedObjectsRawDocParseOptions { + /** + * Optional setting to allow for lax handling of the raw document ID and namespace field. This is needed when a previously + * single-namespace object type is converted to a multi-namespace object type, and it is only intended to be used during upgrade + * migrations. + * + * If not specified, the default treatment is `strict`. + */ + namespaceTreatment?: 'strict' | 'lax'; +} diff --git a/src/core/server/saved_objects/service/lib/included_fields.test.ts b/src/core/server/saved_objects/service/lib/included_fields.test.ts index 6b00816e4c17b..3f2a2d677c42d 100644 --- a/src/core/server/saved_objects/service/lib/included_fields.test.ts +++ b/src/core/server/saved_objects/service/lib/included_fields.test.ts @@ -8,7 +8,7 @@ import { includedFields } from './included_fields'; -const BASE_FIELD_COUNT = 9; +const BASE_FIELD_COUNT = 10; describe('includedFields', () => { it('returns undefined if fields are not provided', () => { @@ -32,6 +32,7 @@ Array [ "type", "references", "migrationVersion", + "coreMigrationVersion", "updated_at", "originId", "foo", @@ -66,6 +67,7 @@ Array [ "type", "references", "migrationVersion", + "coreMigrationVersion", "updated_at", "originId", "foo", diff --git a/src/core/server/saved_objects/service/lib/included_fields.ts b/src/core/server/saved_objects/service/lib/included_fields.ts index 3e11d8d8ad4ef..7aaca2caf003f 100644 --- a/src/core/server/saved_objects/service/lib/included_fields.ts +++ b/src/core/server/saved_objects/service/lib/included_fields.ts @@ -30,6 +30,7 @@ export function includedFields(type: string | string[] = '*', fields?: string[] .concat('type') .concat('references') .concat('migrationVersion') + .concat('coreMigrationVersion') .concat('updated_at') .concat('originId') .concat(fields); // v5 compatibility diff --git a/src/core/server/saved_objects/service/lib/repository.mock.ts b/src/core/server/saved_objects/service/lib/repository.mock.ts index f5b7d30aee4fd..93e73f3255b87 100644 --- a/src/core/server/saved_objects/service/lib/repository.mock.ts +++ b/src/core/server/saved_objects/service/lib/repository.mock.ts @@ -17,6 +17,7 @@ const create = (): jest.Mocked => ({ bulkGet: jest.fn(), find: jest.fn(), get: jest.fn(), + resolve: jest.fn(), update: jest.fn(), addToNamespaces: jest.fn(), deleteFromNamespaces: jest.fn(), diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index d8cdec1e0b8a5..216e1c4bd2d3c 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -13,6 +13,7 @@ import { ALL_NAMESPACES_STRING } from './utils'; import { SavedObjectsSerializer } from '../../serialization'; import { encodeHitVersion } from '../../version'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; +import { LEGACY_URL_ALIAS_TYPE } from '../../object_types'; import { DocumentMigrator } from '../../migrations/core/document_migrator'; import { mockKibanaMigrator } from '../../migrations/kibana/kibana_migrator.mock'; import { elasticsearchClientMock } from '../../../elasticsearch/client/mocks'; @@ -44,6 +45,7 @@ describe('SavedObjectsRepository', () => { const mockVersionProps = { _seq_no: 1, _primary_term: 1 }; const mockVersion = encodeHitVersion(mockVersionProps); + const KIBANA_VERSION = '2.0.0'; const CUSTOM_INDEX_TYPE = 'customIndex'; const NAMESPACE_AGNOSTIC_TYPE = 'globalType'; const MULTI_NAMESPACE_TYPE = 'shareableType'; @@ -142,7 +144,7 @@ describe('SavedObjectsRepository', () => { const documentMigrator = new DocumentMigrator({ typeRegistry: registry, - kibanaVersion: '2.0.0', + kibanaVersion: KIBANA_VERSION, log: {}, }); @@ -216,6 +218,7 @@ describe('SavedObjectsRepository', () => { rawToSavedObject: jest.fn(), savedObjectToRaw: jest.fn(), generateRawId: jest.fn(), + generateRawLegacyUrlAliasId: jest.fn(), trimIdPrefix: jest.fn(), }; const _serializer = new SavedObjectsSerializer(registry); @@ -501,6 +504,7 @@ describe('SavedObjectsRepository', () => { const expectSuccessResult = (obj) => ({ ...obj, migrationVersion: { [obj.type]: '1.1.1' }, + coreMigrationVersion: KIBANA_VERSION, version: mockVersion, namespaces: obj.namespaces ?? [obj.namespace ?? 'default'], ...mockTimestampFields, @@ -954,6 +958,7 @@ describe('SavedObjectsRepository', () => { ...response.items[0].create, _source: { ...response.items[0].create._source, + coreMigrationVersion: '2.0.0', // the document migrator adds this to all objects before creation namespaces: response.items[0].create._source.namespaces, }, _id: expect.stringMatching(/^myspace:config:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/), @@ -962,6 +967,7 @@ describe('SavedObjectsRepository', () => { ...response.items[1].create, _source: { ...response.items[1].create._source, + coreMigrationVersion: '2.0.0', // the document migrator adds this to all objects before creation namespaces: response.items[1].create._source.namespaces, }, }); @@ -2140,6 +2146,7 @@ describe('SavedObjectsRepository', () => { references, namespaces: [namespace ?? 'default'], migrationVersion: { [type]: '1.1.1' }, + coreMigrationVersion: KIBANA_VERSION, }); }); }); @@ -2724,6 +2731,7 @@ describe('SavedObjectsRepository', () => { 'type', 'references', 'migrationVersion', + 'coreMigrationVersion', 'updated_at', 'originId', 'title', @@ -3254,6 +3262,231 @@ describe('SavedObjectsRepository', () => { }); }); + describe('#resolve', () => { + const type = 'index-pattern'; + const id = 'logstash-*'; + const aliasTargetId = 'some-other-id'; // only used for 'aliasMatch' and 'conflict' outcomes + const namespace = 'foo-namespace'; + + const getMockAliasDocument = (resolveCounter) => ({ + body: { + get: { + _source: { + [LEGACY_URL_ALIAS_TYPE]: { + targetId: aliasTargetId, + ...(resolveCounter && { resolveCounter }), + // other fields are not used by the repository + }, + }, + }, + }, + }); + + describe('outcomes', () => { + describe('error', () => { + const expectNotFoundError = async (type, id, options) => { + await expect(savedObjectsRepository.resolve(type, id, options)).rejects.toThrowError( + createGenericNotFoundError(type, id) + ); + }; + + it('because type is invalid', async () => { + await expectNotFoundError('unknownType', id); + expect(client.update).not.toHaveBeenCalled(); + expect(client.get).not.toHaveBeenCalled(); + expect(client.mget).not.toHaveBeenCalled(); + }); + + it('because type is hidden', async () => { + await expectNotFoundError(HIDDEN_TYPE, id); + expect(client.update).not.toHaveBeenCalled(); + expect(client.get).not.toHaveBeenCalled(); + expect(client.mget).not.toHaveBeenCalled(); + }); + + it('because alias is not used and actual object is not found', async () => { + const options = { namespace: undefined }; + const response = { found: false }; + client.get.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(response) // for actual target + ); + + await expectNotFoundError(type, id, options); + expect(client.update).not.toHaveBeenCalled(); + expect(client.get).toHaveBeenCalledTimes(1); // retrieved actual target + expect(client.mget).not.toHaveBeenCalled(); + }); + + it('because actual object and alias object are both not found', async () => { + const options = { namespace }; + const objectResults = [ + { type, id, found: false }, + { type, id: aliasTargetId, found: false }, + ]; + client.update.mockResolvedValueOnce(getMockAliasDocument()); // for alias object + const response = getMockMgetResponse(objectResults, options.namespace); + client.mget.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(response) // for actual target + ); + + await expectNotFoundError(type, id, options); + expect(client.update).toHaveBeenCalledTimes(1); // retrieved alias object + expect(client.get).not.toHaveBeenCalled(); + expect(client.mget).toHaveBeenCalledTimes(1); // retrieved actual target and alias target + }); + }); + + describe('exactMatch', () => { + it('because namespace is undefined', async () => { + const options = { namespace: undefined }; + const response = getMockGetResponse({ type, id }); + client.get.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(response) // for actual target + ); + + const result = await savedObjectsRepository.resolve(type, id, options); + expect(client.update).not.toHaveBeenCalled(); + expect(client.get).toHaveBeenCalledTimes(1); // retrieved actual target + expect(client.mget).not.toHaveBeenCalled(); + expect(result).toEqual({ + saved_object: expect.objectContaining({ type, id }), + outcome: 'exactMatch', + }); + }); + + describe('because alias is not used', () => { + const expectExactMatchResult = async (aliasResult) => { + const options = { namespace }; + client.update.mockResolvedValueOnce(aliasResult); // for alias object + const response = getMockGetResponse({ type, id }, options.namespace); + client.get.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(response) // for actual target + ); + + const result = await savedObjectsRepository.resolve(type, id, options); + expect(client.update).toHaveBeenCalledTimes(1); // retrieved alias object + expect(client.get).toHaveBeenCalledTimes(1); // retrieved actual target + expect(client.mget).not.toHaveBeenCalled(); + expect(result).toEqual({ + saved_object: expect.objectContaining({ type, id }), + outcome: 'exactMatch', + }); + }; + + it('since alias call resulted in 404', async () => { + await expectExactMatchResult({ statusCode: 404 }); + }); + + it('since alias is not found', async () => { + await expectExactMatchResult({ body: { get: { found: false } } }); + }); + + it('since alias is disabled', async () => { + await expectExactMatchResult({ + body: { get: { _source: { [LEGACY_URL_ALIAS_TYPE]: { disabled: true } } } }, + }); + }); + }); + + describe('because alias is used', () => { + const expectExactMatchResult = async (objectResults) => { + const options = { namespace }; + client.update.mockResolvedValueOnce(getMockAliasDocument()); // for alias object + const response = getMockMgetResponse(objectResults, options.namespace); + client.mget.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(response) // for actual target and alias target + ); + + const result = await savedObjectsRepository.resolve(type, id, options); + expect(client.update).toHaveBeenCalledTimes(1); // retrieved alias object + expect(client.get).not.toHaveBeenCalled(); + expect(client.mget).toHaveBeenCalledTimes(1); // retrieved actual target and alias target + expect(result).toEqual({ + saved_object: expect.objectContaining({ type, id }), + outcome: 'exactMatch', + }); + }; + + it('but alias target is not found', async () => { + const objects = [ + { type, id }, + { type, id: aliasTargetId, found: false }, + ]; + await expectExactMatchResult(objects); + }); + + it('but alias target does not exist in this namespace', async () => { + const objects = [ + { type: MULTI_NAMESPACE_TYPE, id }, // correct namespace field is added by getMockMgetResponse + { type: MULTI_NAMESPACE_TYPE, id: aliasTargetId, namespace: `not-${namespace}` }, // overrides namespace field that would otherwise be added by getMockMgetResponse + ]; + await expectExactMatchResult(objects); + }); + }); + }); + + describe('aliasMatch', () => { + const expectAliasMatchResult = async (objectResults) => { + const options = { namespace }; + client.update.mockResolvedValueOnce(getMockAliasDocument()); // for alias object + const response = getMockMgetResponse(objectResults, options.namespace); + client.mget.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(response) // for actual target and alias target + ); + + const result = await savedObjectsRepository.resolve(type, id, options); + expect(client.update).toHaveBeenCalledTimes(1); // retrieved alias object + expect(client.get).not.toHaveBeenCalled(); + expect(client.mget).toHaveBeenCalledTimes(1); // retrieved actual target and alias target + expect(result).toEqual({ + saved_object: expect.objectContaining({ type, id: aliasTargetId }), + outcome: 'aliasMatch', + }); + }; + + it('because actual target is not found', async () => { + const objects = [ + { type, id, found: false }, + { type, id: aliasTargetId }, + ]; + await expectAliasMatchResult(objects); + }); + + it('because actual target does not exist in this namespace', async () => { + const objects = [ + { type: MULTI_NAMESPACE_TYPE, id, namespace: `not-${namespace}` }, // overrides namespace field that would otherwise be added by getMockMgetResponse + { type: MULTI_NAMESPACE_TYPE, id: aliasTargetId }, // correct namespace field is added by getMockMgetResponse + ]; + await expectAliasMatchResult(objects); + }); + }); + + describe('conflict', () => { + it('because actual target and alias target are both found', async () => { + const options = { namespace }; + const objectResults = [ + { type, id }, // correct namespace field is added by getMockMgetResponse + { type, id: aliasTargetId }, // correct namespace field is added by getMockMgetResponse + ]; + client.update.mockResolvedValueOnce(getMockAliasDocument()); // for alias object + const response = getMockMgetResponse(objectResults, options.namespace); + client.mget.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(response) // for actual target and alias target + ); + + const result = await savedObjectsRepository.resolve(type, id, options); + expect(client.update).toHaveBeenCalledTimes(1); // retrieved alias object + expect(client.get).not.toHaveBeenCalled(); + expect(client.mget).toHaveBeenCalledTimes(1); // retrieved actual target and alias target + expect(result).toEqual({ + saved_object: expect.objectContaining({ type, id }), + outcome: 'conflict', + }); + }); + }); + }); + }); + describe('#incrementCounter', () => { const type = 'config'; const id = 'one'; diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 685760e81a2b7..2993d4234bd2e 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -47,6 +47,7 @@ import { SavedObjectsDeleteFromNamespacesResponse, SavedObjectsRemoveReferencesToOptions, SavedObjectsRemoveReferencesToResponse, + SavedObjectsResolveResponse, } from '../saved_objects_client'; import { SavedObject, @@ -55,6 +56,7 @@ import { SavedObjectsMigrationVersion, MutatingOperationRefreshSetting, } from '../../types'; +import { LegacyUrlAlias, LEGACY_URL_ALIAS_TYPE } from '../../object_types'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { validateConvertFilterToKueryNode } from './filter_utils'; import { @@ -920,25 +922,7 @@ export class SavedObjectsRepository { } as any) as SavedObject; } - const { originId, updated_at: updatedAt } = doc._source; - let namespaces = []; - if (!this._registry.isNamespaceAgnostic(type)) { - namespaces = doc._source.namespaces ?? [ - SavedObjectsUtils.namespaceIdToString(doc._source.namespace), - ]; - } - - return { - id, - type, - namespaces, - ...(originId && { originId }), - ...(updatedAt && { updated_at: updatedAt }), - version: encodeHitVersion(doc), - attributes: doc._source[type], - references: doc._source.references || [], - migrationVersion: doc._source.migrationVersion, - }; + return this.getSavedObjectFromSource(type, id, doc); }), }; } @@ -978,26 +962,122 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - const { originId, updated_at: updatedAt } = body._source; + return this.getSavedObjectFromSource(type, id, body); + } - let namespaces: string[] = []; - if (!this._registry.isNamespaceAgnostic(type)) { - namespaces = body._source.namespaces ?? [ - SavedObjectsUtils.namespaceIdToString(body._source.namespace), - ]; + /** + * Resolves a single object, using any legacy URL alias if it exists + * + * @param {string} type + * @param {string} id + * @param {object} [options={}] + * @property {string} [options.namespace] + * @returns {promise} - { saved_object, outcome } + */ + async resolve( + type: string, + id: string, + options: SavedObjectsBaseOptions = {} + ): Promise> { + if (!this._allowedTypes.includes(type)) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - return { - id, - type, - namespaces, - ...(originId && { originId }), - ...(updatedAt && { updated_at: updatedAt }), - version: encodeHitVersion(body), - attributes: body._source[type], - references: body._source.references || [], - migrationVersion: body._source.migrationVersion, - }; + const namespace = normalizeNamespace(options.namespace); + if (namespace === undefined) { + // legacy URL aliases cannot exist for the default namespace; just attempt to get the object + return this.resolveExactMatch(type, id, options); + } + + const rawAliasId = this._serializer.generateRawLegacyUrlAliasId(namespace, type, id); + const time = this._getCurrentTime(); + + // retrieve the alias, and if it is not disabled, update it + const aliasResponse = await this.client.update( + { + id: rawAliasId, + index: this.getIndexForType(LEGACY_URL_ALIAS_TYPE), + refresh: false, + _source: 'true', + body: { + script: { + source: ` + if (ctx._source[params.type].disabled != true) { + if (ctx._source[params.type].resolveCounter == null) { + ctx._source[params.type].resolveCounter = 1; + } + else { + ctx._source[params.type].resolveCounter += 1; + } + ctx._source[params.type].lastResolved = params.time; + ctx._source.updated_at = params.time; + } + `, + lang: 'painless', + params: { + type: LEGACY_URL_ALIAS_TYPE, + time, + }, + }, + }, + }, + { ignore: [404] } + ); + + if ( + aliasResponse.statusCode === 404 || + aliasResponse.body.get.found === false || + aliasResponse.body.get._source[LEGACY_URL_ALIAS_TYPE]?.disabled === true + ) { + // no legacy URL alias exists, or one exists but it's disabled; just attempt to get the object + return this.resolveExactMatch(type, id, options); + } + const legacyUrlAlias: LegacyUrlAlias = aliasResponse.body.get._source[LEGACY_URL_ALIAS_TYPE]; + const objectIndex = this.getIndexForType(type); + const bulkGetResponse = await this.client.mget( + { + body: { + docs: [ + { + // attempt to find an exact match for the given ID + _id: this._serializer.generateRawId(namespace, type, id), + _index: objectIndex, + }, + { + // also attempt to find a match for the legacy URL alias target ID + _id: this._serializer.generateRawId(namespace, type, legacyUrlAlias.targetId), + _index: objectIndex, + }, + ], + }, + }, + { ignore: [404] } + ); + + const exactMatchDoc = bulkGetResponse?.body.docs[0]; + const aliasMatchDoc = bulkGetResponse?.body.docs[1]; + const foundExactMatch = + exactMatchDoc.found && this.rawDocExistsInNamespace(exactMatchDoc, namespace); + const foundAliasMatch = + aliasMatchDoc.found && this.rawDocExistsInNamespace(aliasMatchDoc, namespace); + + if (foundExactMatch && foundAliasMatch) { + return { + saved_object: this.getSavedObjectFromSource(type, id, exactMatchDoc), + outcome: 'conflict', + }; + } else if (foundExactMatch) { + return { + saved_object: this.getSavedObjectFromSource(type, id, exactMatchDoc), + outcome: 'exactMatch', + }; + } else if (foundAliasMatch) { + return { + saved_object: this.getSavedObjectFromSource(type, legacyUrlAlias.targetId, aliasMatchDoc), + outcome: 'aliasMatch', + }; + } + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } /** @@ -1718,7 +1798,7 @@ export class SavedObjectsRepository { if (this._registry.isSingleNamespace(type)) { savedObject.namespaces = [SavedObjectsUtils.namespaceIdToString(namespace)]; } - return omit(savedObject, 'namespace') as SavedObject; + return omit(savedObject, ['namespace']) as SavedObject; } /** @@ -1814,6 +1894,43 @@ export class SavedObjectsRepository { } return body as SavedObjectsRawDoc; } + + private getSavedObjectFromSource( + type: string, + id: string, + doc: { _seq_no: number; _primary_term: number; _source: SavedObjectsRawDocSource } + ): SavedObject { + const { originId, updated_at: updatedAt } = doc._source; + + let namespaces: string[] = []; + if (!this._registry.isNamespaceAgnostic(type)) { + namespaces = doc._source.namespaces ?? [ + SavedObjectsUtils.namespaceIdToString(doc._source.namespace), + ]; + } + + return { + id, + type, + namespaces, + ...(originId && { originId }), + ...(updatedAt && { updated_at: updatedAt }), + version: encodeHitVersion(doc), + attributes: doc._source[type], + references: doc._source.references || [], + migrationVersion: doc._source.migrationVersion, + coreMigrationVersion: doc._source.coreMigrationVersion, + }; + } + + private async resolveExactMatch( + type: string, + id: string, + options: SavedObjectsBaseOptions + ): Promise> { + const object = await this.get(type, id, options); + return { saved_object: object, outcome: 'exactMatch' }; + } } function getBulkOperationError(error: { type: string; reason?: string }, type: string, id: string) { diff --git a/src/core/server/saved_objects/service/saved_objects_client.mock.ts b/src/core/server/saved_objects/service/saved_objects_client.mock.ts index 75269d3a77f65..2dd2bcce7a245 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.mock.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.mock.ts @@ -20,6 +20,7 @@ const create = () => bulkGet: jest.fn(), find: jest.fn(), get: jest.fn(), + resolve: jest.fn(), update: jest.fn(), addToNamespaces: jest.fn(), deleteFromNamespaces: jest.fn(), diff --git a/src/core/server/saved_objects/service/saved_objects_client.test.js b/src/core/server/saved_objects/service/saved_objects_client.test.js index 5cee6bc274f9b..e6409fb853bd8 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.test.js +++ b/src/core/server/saved_objects/service/saved_objects_client.test.js @@ -115,6 +115,22 @@ test(`#get`, async () => { expect(result).toBe(returnValue); }); +test(`#resolve`, async () => { + const returnValue = Symbol(); + const mockRepository = { + resolve: jest.fn().mockResolvedValue(returnValue), + }; + const client = new SavedObjectsClient(mockRepository); + + const type = Symbol(); + const id = Symbol(); + const options = Symbol(); + const result = await client.resolve(type, id, options); + + expect(mockRepository.resolve).toHaveBeenCalledWith(type, id, options); + expect(result).toBe(returnValue); +}); + test(`#update`, async () => { const returnValue = Symbol(); const mockRepository = { diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index ca1d404e010bd..d17f6b082096f 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -34,6 +34,16 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { version?: string; /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; + /** + * A semver value that is used when upgrading objects between Kibana versions. If undefined, this will be automatically set to the current + * Kibana version when the object is created. If this is set to a non-semver value, or it is set to a semver value greater than the + * current Kibana version, it will result in an error. + * + * @remarks + * Do not attempt to set this manually. It should only be used if you retrieved an existing object that had the `coreMigrationVersion` + * field set and you want to create it again. + */ + coreMigrationVersion?: string; references?: SavedObjectReference[]; /** The Elasticsearch Refresh setting for this operation */ refresh?: MutatingOperationRefreshSetting; @@ -60,6 +70,16 @@ export interface SavedObjectsBulkCreateObject { references?: SavedObjectReference[]; /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; + /** + * A semver value that is used when upgrading objects between Kibana versions. If undefined, this will be automatically set to the current + * Kibana version when the object is created. If this is set to a non-semver value, or it is set to a semver value greater than the + * current Kibana version, it will result in an error. + * + * @remarks + * Do not attempt to set this manually. It should only be used if you retrieved an existing object that had the `coreMigrationVersion` + * field set and you want to create it again. + */ + coreMigrationVersion?: string; /** Optional ID of the original saved object, if this object's `id` was regenerated */ originId?: string; /** @@ -273,6 +293,24 @@ export interface SavedObjectsUpdateResponse references: SavedObjectReference[] | undefined; } +/** + * + * @public + */ +export interface SavedObjectsResolveResponse { + saved_object: SavedObject; + /** + * The outcome for a successful `resolve` call is one of the following values: + * + * * `'exactMatch'` -- One document exactly matched the given ID. + * * `'aliasMatch'` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different + * than the given ID. + * * `'conflict'` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the + * `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID. + */ + outcome: 'exactMatch' | 'aliasMatch' | 'conflict'; +} + /** * * @public @@ -379,6 +417,21 @@ export class SavedObjectsClient { return await this._repository.get(type, id, options); } + /** + * Resolves a single object, using any legacy URL alias if it exists + * + * @param type - The type of SavedObject to retrieve + * @param id - The ID of the SavedObject to retrieve + * @param options + */ + async resolve( + type: string, + id: string, + options: SavedObjectsBaseOptions = {} + ): Promise> { + return await this._repository.resolve(type, id, options); + } + /** * Updates an SavedObject * diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index cbd8b415d9d31..7fab03aab4d0f 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -241,6 +241,41 @@ export interface SavedObjectsType { * An optional map of {@link SavedObjectMigrationFn | migrations} or a function returning a map of {@link SavedObjectMigrationFn | migrations} to be used to migrate the type. */ migrations?: SavedObjectMigrationMap | (() => SavedObjectMigrationMap); + /** + * If defined, objects of this type will be converted to multi-namespace objects when migrating to this version. + * + * Requirements: + * + * 1. This string value must be a valid semver version + * 2. This type must have previously specified {@link SavedObjectsNamespaceType | `namespaceType: 'single'`} + * 3. This type must also specify {@link SavedObjectsNamespaceType | `namespaceType: 'multiple'`} + * + * Example of a single-namespace type in 7.10: + * + * ```ts + * { + * name: 'foo', + * hidden: false, + * namespaceType: 'single', + * mappings: {...} + * } + * ``` + * + * Example after converting to a multi-namespace type in 7.11: + * + * ```ts + * { + * name: 'foo', + * hidden: false, + * namespaceType: 'multiple', + * mappings: {...}, + * convertToMultiNamespaceTypeVersion: '7.11.0' + * } + * ``` + * + * Note: a migration function can be optionally specified for the same version. + */ + convertToMultiNamespaceTypeVersion?: string; /** * An optional {@link SavedObjectsTypeManagementDefinition | saved objects management section} definition for the type. */ diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 3a8d7f4f0b0ff..d0ba6aa1900c7 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -680,6 +680,20 @@ export interface CoreUsageStats { // (undocumented) 'apiCalls.savedObjectsImport.total'?: number; // (undocumented) + 'apiCalls.savedObjectsResolve.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolve.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolve.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolve.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolve.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolve.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolve.total'?: number; + // (undocumented) 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.no'?: number; // (undocumented) 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.yes'?: number; @@ -2033,6 +2047,7 @@ export type SafeRouteMethod = 'get' | 'options'; // @public (undocumented) export interface SavedObject { attributes: T; + coreMigrationVersion?: string; // Warning: (ae-forgotten-export) The symbol "SavedObjectError" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -2116,6 +2131,7 @@ export interface SavedObjectsBaseOptions { export interface SavedObjectsBulkCreateObject { // (undocumented) attributes: T; + coreMigrationVersion?: string; // (undocumented) id?: string; initialNamespaces?: string[]; @@ -2206,6 +2222,7 @@ export class SavedObjectsClient { find(options: SavedObjectsFindOptions): Promise>; get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; removeReferencesTo(type: string, id: string, options?: SavedObjectsRemoveReferencesToOptions): Promise; + resolve(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; } @@ -2276,6 +2293,7 @@ export interface SavedObjectsCoreFieldMapping { // @public (undocumented) export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { + coreMigrationVersion?: string; id?: string; initialNamespaces?: string[]; migrationVersion?: SavedObjectsMigrationVersion; @@ -2711,6 +2729,11 @@ export interface SavedObjectsRawDoc { _source: SavedObjectsRawDocSource; } +// @public +export interface SavedObjectsRawDocParseOptions { + namespaceTreatment?: 'strict' | 'lax'; +} + // @public (undocumented) export interface SavedObjectsRemoveReferencesToOptions extends SavedObjectsBaseOptions { refresh?: boolean; @@ -2741,6 +2764,7 @@ export class SavedObjectsRepository { get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; incrementCounter(type: string, id: string, counterFields: Array, options?: SavedObjectsIncrementCounterOptions): Promise>; removeReferencesTo(type: string, id: string, options?: SavedObjectsRemoveReferencesToOptions): Promise; + resolve(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; } @@ -2758,13 +2782,21 @@ export interface SavedObjectsResolveImportErrorsOptions { retries: SavedObjectsImportRetry[]; } +// @public (undocumented) +export interface SavedObjectsResolveResponse { + outcome: 'exactMatch' | 'aliasMatch' | 'conflict'; + // (undocumented) + saved_object: SavedObject; +} + // @public export class SavedObjectsSerializer { // @internal constructor(registry: ISavedObjectTypeRegistry); generateRawId(namespace: string | undefined, type: string, id: string): string; - isRawSavedObject(rawDoc: SavedObjectsRawDoc): boolean; - rawToSavedObject(doc: SavedObjectsRawDoc): SavedObjectSanitizedDoc; + generateRawLegacyUrlAliasId(namespace: string, type: string, id: string): string; + isRawSavedObject(doc: SavedObjectsRawDoc, options?: SavedObjectsRawDocParseOptions): boolean; + rawToSavedObject(doc: SavedObjectsRawDoc, options?: SavedObjectsRawDocParseOptions): SavedObjectSanitizedDoc; savedObjectToRaw(savedObj: SavedObjectSanitizedDoc): SavedObjectsRawDoc; } @@ -2799,6 +2831,7 @@ export interface SavedObjectStatusMeta { // @public (undocumented) export interface SavedObjectsType { convertToAliasScript?: string; + convertToMultiNamespaceTypeVersion?: string; hidden: boolean; indexPattern?: string; management?: SavedObjectsTypeManagementDefinition; diff --git a/src/core/types/saved_objects.ts b/src/core/types/saved_objects.ts index 38b8ad0fc5325..c19f1febc97b1 100644 --- a/src/core/types/saved_objects.ts +++ b/src/core/types/saved_objects.ts @@ -82,6 +82,8 @@ export interface SavedObject { references: SavedObjectReference[]; /** {@inheritdoc SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; + /** A semver value that is used when upgrading objects between Kibana versions. */ + coreMigrationVersion?: string; /** Namespace(s) that this saved object exists in. This attribute is only used for multi-namespace saved object types. */ namespaces?: string[]; /** diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 15594dc80c888..84a82511d5a5e 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -1118,7 +1118,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; diff --git a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts index 1bc4c70e77064..9b83cdd69a545 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts @@ -154,6 +154,13 @@ export function getCoreUsageCollector( 'apiCalls.savedObjectsGet.namespace.custom.total': { type: 'long' }, 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes': { type: 'long' }, 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsResolve.total': { type: 'long' }, + 'apiCalls.savedObjectsResolve.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsResolve.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsResolve.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsResolve.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsResolve.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsResolve.namespace.custom.kibanaRequest.no': { type: 'long' }, 'apiCalls.savedObjectsUpdate.total': { type: 'long' }, 'apiCalls.savedObjectsUpdate.namespace.default.total': { type: 'long' }, 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes': { type: 'long' }, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index bed142f165d64..50a08d96de951 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -3864,6 +3864,27 @@ "apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no": { "type": "long" }, + "apiCalls.savedObjectsResolve.total": { + "type": "long" + }, + "apiCalls.savedObjectsResolve.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsResolve.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsResolve.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsResolve.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsResolve.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsResolve.namespace.custom.kibanaRequest.no": { + "type": "long" + }, "apiCalls.savedObjectsUpdate.total": { "type": "long" }, diff --git a/test/api_integration/apis/saved_objects/bulk_create.ts b/test/api_integration/apis/saved_objects/bulk_create.ts index 903332a0a930f..a548172365b07 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.ts +++ b/test/api_integration/apis/saved_objects/bulk_create.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -21,6 +22,7 @@ export default function ({ getService }: FtrProviderContext) { attributes: { title: 'An existing visualization', }, + coreMigrationVersion: '1.2.3', }, { type: 'dashboard', @@ -32,6 +34,12 @@ export default function ({ getService }: FtrProviderContext) { ]; describe('_bulk_create', () => { + let KIBANA_VERSION: string; + + before(async () => { + KIBANA_VERSION = await getKibanaVersion(getService); + }); + describe('with kibana index', () => { before(() => esArchiver.load('saved_objects/basic')); after(() => esArchiver.unload('saved_objects/basic')); @@ -65,6 +73,7 @@ export default function ({ getService }: FtrProviderContext) { migrationVersion: { dashboard: resp.body.saved_objects[1].migrationVersion.dashboard, }, + coreMigrationVersion: KIBANA_VERSION, references: [], namespaces: ['default'], }, @@ -112,6 +121,7 @@ export default function ({ getService }: FtrProviderContext) { migrationVersion: { visualization: resp.body.saved_objects[0].migrationVersion.visualization, }, + coreMigrationVersion: KIBANA_VERSION, // updated from 1.2.3 to the latest kibana version }, { type: 'dashboard', @@ -126,6 +136,7 @@ export default function ({ getService }: FtrProviderContext) { migrationVersion: { dashboard: resp.body.saved_objects[1].migrationVersion.dashboard, }, + coreMigrationVersion: KIBANA_VERSION, }, ], }); diff --git a/test/api_integration/apis/saved_objects/bulk_get.ts b/test/api_integration/apis/saved_objects/bulk_get.ts index e552c08a58cf0..46631225f8e8a 100644 --- a/test/api_integration/apis/saved_objects/bulk_get.ts +++ b/test/api_integration/apis/saved_objects/bulk_get.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -30,6 +31,12 @@ export default function ({ getService }: FtrProviderContext) { ]; describe('_bulk_get', () => { + let KIBANA_VERSION: string; + + before(async () => { + KIBANA_VERSION = await getKibanaVersion(getService); + }); + describe('with kibana index', () => { before(() => esArchiver.load('saved_objects/basic')); after(() => esArchiver.unload('saved_objects/basic')); @@ -58,6 +65,7 @@ export default function ({ getService }: FtrProviderContext) { resp.body.saved_objects[0].attributes.kibanaSavedObjectMeta, }, migrationVersion: resp.body.saved_objects[0].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, namespaces: ['default'], references: [ { @@ -87,6 +95,7 @@ export default function ({ getService }: FtrProviderContext) { }, namespaces: ['default'], migrationVersion: resp.body.saved_objects[2].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, references: [], }, ], diff --git a/test/api_integration/apis/saved_objects/create.ts b/test/api_integration/apis/saved_objects/create.ts index b1cd5a8dfdae4..551e082630e8f 100644 --- a/test/api_integration/apis/saved_objects/create.ts +++ b/test/api_integration/apis/saved_objects/create.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -15,6 +16,12 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); describe('create', () => { + let KIBANA_VERSION: string; + + before(async () => { + KIBANA_VERSION = await getKibanaVersion(getService); + }); + describe('with kibana index', () => { before(() => esArchiver.load('saved_objects/basic')); after(() => esArchiver.unload('saved_objects/basic')); @@ -42,6 +49,7 @@ export default function ({ getService }: FtrProviderContext) { id: resp.body.id, type: 'visualization', migrationVersion: resp.body.migrationVersion, + coreMigrationVersion: KIBANA_VERSION, updated_at: resp.body.updated_at, version: resp.body.version, attributes: { @@ -53,6 +61,21 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.body.migrationVersion).to.be.ok(); }); }); + + it('result should be updated to the latest coreMigrationVersion', async () => { + await supertest + .post(`/api/saved_objects/visualization`) + .send({ + attributes: { + title: 'My favorite vis', + }, + coreMigrationVersion: '1.2.3', + }) + .expect(200) + .then((resp) => { + expect(resp.body.coreMigrationVersion).to.eql(KIBANA_VERSION); + }); + }); }); describe('without kibana index', () => { @@ -86,6 +109,7 @@ export default function ({ getService }: FtrProviderContext) { id: resp.body.id, type: 'visualization', migrationVersion: resp.body.migrationVersion, + coreMigrationVersion: KIBANA_VERSION, updated_at: resp.body.updated_at, version: resp.body.version, attributes: { @@ -99,6 +123,21 @@ export default function ({ getService }: FtrProviderContext) { expect((await es.indices.exists({ index: '.kibana' })).body).to.be(true); }); + + it('result should have the latest coreMigrationVersion', async () => { + await supertest + .post(`/api/saved_objects/visualization`) + .send({ + attributes: { + title: 'My favorite vis', + }, + coreMigrationVersion: '1.2.3', + }) + .expect(200) + .then((resp) => { + expect(resp.body.coreMigrationVersion).to.eql(KIBANA_VERSION); + }); + }); }); }); } diff --git a/test/api_integration/apis/saved_objects/export.ts b/test/api_integration/apis/saved_objects/export.ts index a84f3050fdd17..a45191f24d872 100644 --- a/test/api_integration/apis/saved_objects/export.ts +++ b/test/api_integration/apis/saved_objects/export.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../ftr_provider_context'; +import { getKibanaVersion } from './lib/saved_objects_test_utils'; function ndjsonToObject(input: string) { return input.split('\n').map((str) => JSON.parse(str)); @@ -18,6 +19,12 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); describe('export', () => { + let KIBANA_VERSION: string; + + before(async () => { + KIBANA_VERSION = await getKibanaVersion(getService); + }); + describe('with kibana index', () => { describe('basic amount of saved objects', () => { before(() => esArchiver.load('saved_objects/basic')); @@ -312,6 +319,7 @@ export default function ({ getService }: FtrProviderContext) { }, id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', migrationVersion: objects[0].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, references: [ { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', @@ -371,6 +379,7 @@ export default function ({ getService }: FtrProviderContext) { }, id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', migrationVersion: objects[0].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, references: [ { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', @@ -435,6 +444,7 @@ export default function ({ getService }: FtrProviderContext) { }, id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', migrationVersion: objects[0].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, references: [ { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', diff --git a/test/api_integration/apis/saved_objects/find.ts b/test/api_integration/apis/saved_objects/find.ts index a3ce70888049c..7aa4de86baa69 100644 --- a/test/api_integration/apis/saved_objects/find.ts +++ b/test/api_integration/apis/saved_objects/find.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; import { SavedObject } from '../../../../src/core/server'; +import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -16,6 +17,12 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); describe('find', () => { + let KIBANA_VERSION: string; + + before(async () => { + KIBANA_VERSION = await getKibanaVersion(getService); + }); + describe('with kibana index', () => { before(() => esArchiver.load('saved_objects/basic')); after(() => esArchiver.unload('saved_objects/basic')); @@ -39,6 +46,7 @@ export default function ({ getService }: FtrProviderContext) { }, score: 0, migrationVersion: resp.body.saved_objects[0].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, namespaces: ['default'], references: [ { @@ -134,6 +142,7 @@ export default function ({ getService }: FtrProviderContext) { title: 'Count of requests', }, migrationVersion: resp.body.saved_objects[0].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, namespaces: ['default'], score: 0, references: [ @@ -170,6 +179,7 @@ export default function ({ getService }: FtrProviderContext) { title: 'Count of requests', }, migrationVersion: resp.body.saved_objects[0].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, namespaces: ['default'], score: 0, references: [ @@ -187,6 +197,7 @@ export default function ({ getService }: FtrProviderContext) { }, id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', migrationVersion: resp.body.saved_objects[0].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, namespaces: ['foo-ns'], references: [ { @@ -202,7 +213,6 @@ export default function ({ getService }: FtrProviderContext) { }, ], }); - expect(resp.body.saved_objects[0].migrationVersion).to.be.ok(); })); }); @@ -244,6 +254,7 @@ export default function ({ getService }: FtrProviderContext) { }, ], migrationVersion: resp.body.saved_objects[0].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, updated_at: '2017-09-21T18:51:23.794Z', version: 'WzIsMV0=', }, diff --git a/test/api_integration/apis/saved_objects/get.ts b/test/api_integration/apis/saved_objects/get.ts index 7134917122177..ff47b9df218dc 100644 --- a/test/api_integration/apis/saved_objects/get.ts +++ b/test/api_integration/apis/saved_objects/get.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -15,6 +16,12 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); describe('get', () => { + let KIBANA_VERSION: string; + + before(async () => { + KIBANA_VERSION = await getKibanaVersion(getService); + }); + describe('with kibana index', () => { before(() => esArchiver.load('saved_objects/basic')); after(() => esArchiver.unload('saved_objects/basic')); @@ -30,6 +37,7 @@ export default function ({ getService }: FtrProviderContext) { updated_at: '2017-09-21T18:51:23.794Z', version: resp.body.version, migrationVersion: resp.body.migrationVersion, + coreMigrationVersion: KIBANA_VERSION, attributes: { title: 'Count of requests', description: '', diff --git a/test/api_integration/apis/saved_objects/index.ts b/test/api_integration/apis/saved_objects/index.ts index 0e07b3c1ed060..2f63a4a7cce0a 100644 --- a/test/api_integration/apis/saved_objects/index.ts +++ b/test/api_integration/apis/saved_objects/index.ts @@ -12,15 +12,16 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('saved_objects', () => { loadTestFile(require.resolve('./bulk_create')); loadTestFile(require.resolve('./bulk_get')); + loadTestFile(require.resolve('./bulk_update')); loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./delete')); loadTestFile(require.resolve('./export')); loadTestFile(require.resolve('./find')); loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./import')); + loadTestFile(require.resolve('./migrations')); + loadTestFile(require.resolve('./resolve')); loadTestFile(require.resolve('./resolve_import_errors')); loadTestFile(require.resolve('./update')); - loadTestFile(require.resolve('./bulk_update')); - loadTestFile(require.resolve('./migrations')); }); } diff --git a/test/api_integration/apis/saved_objects/lib/saved_objects_test_utils.ts b/test/api_integration/apis/saved_objects/lib/saved_objects_test_utils.ts new file mode 100644 index 0000000000000..e278bd3d50034 --- /dev/null +++ b/test/api_integration/apis/saved_objects/lib/saved_objects_test_utils.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export async function getKibanaVersion(getService: FtrProviderContext['getService']) { + const kibanaServer = getService('kibanaServer'); + const kibanaVersion = await kibanaServer.version.get(); + expect(typeof kibanaVersion).to.eql('string'); + expect(kibanaVersion.length).to.be.greaterThan(0); + return kibanaVersion; +} diff --git a/test/api_integration/apis/saved_objects/migrations.ts b/test/api_integration/apis/saved_objects/migrations.ts index 9bb820b2f8414..0b06b675f60c0 100644 --- a/test/api_integration/apis/saved_objects/migrations.ts +++ b/test/api_integration/apis/saved_objects/migrations.ts @@ -10,10 +10,11 @@ * Smokescreen tests for core migration logic */ +import uuidv5 from 'uuid/v5'; import { set } from '@elastic/safer-lodash-set'; import _ from 'lodash'; import expect from '@kbn/expect'; -import { ElasticsearchClient, SavedObjectMigrationMap, SavedObjectsType } from 'src/core/server'; +import { ElasticsearchClient, SavedObjectsType } from 'src/core/server'; import { SearchResponse } from '../../../../src/core/server/elasticsearch/client'; import { DocumentMigrator, @@ -28,6 +29,26 @@ import { } from '../../../../src/core/server/saved_objects'; import { FtrProviderContext } from '../../ftr_provider_context'; +const KIBANA_VERSION = '99.9.9'; +const FOO_TYPE: SavedObjectsType = { + name: 'foo', + hidden: false, + namespaceType: 'single', + mappings: { properties: {} }, +}; +const BAR_TYPE: SavedObjectsType = { + name: 'bar', + hidden: false, + namespaceType: 'single', + mappings: { properties: {} }, +}; +const BAZ_TYPE: SavedObjectsType = { + name: 'baz', + hidden: false, + namespaceType: 'single', + mappings: { properties: {} }, +}; + function getLogMock() { return { debug() {}, @@ -61,16 +82,22 @@ export default ({ getService }: FtrProviderContext) => { bar: { properties: { mynum: { type: 'integer' } } }, }; - const migrations: Record = { - foo: { - '1.0.0': (doc) => set(doc, 'attributes.name', doc.attributes.name.toUpperCase()), + const savedObjectTypes: SavedObjectsType[] = [ + { + ...FOO_TYPE, + migrations: { + '1.0.0': (doc) => set(doc, 'attributes.name', doc.attributes.name.toUpperCase()), + }, }, - bar: { - '1.0.0': (doc) => set(doc, 'attributes.nomnom', doc.attributes.nomnom + 1), - '1.3.0': (doc) => set(doc, 'attributes', { mynum: doc.attributes.nomnom }), - '1.9.0': (doc) => set(doc, 'attributes.mynum', doc.attributes.mynum * 2), + { + ...BAR_TYPE, + migrations: { + '1.0.0': (doc) => set(doc, 'attributes.nomnom', doc.attributes.nomnom + 1), + '1.3.0': (doc) => set(doc, 'attributes', { mynum: doc.attributes.nomnom }), + '1.9.0': (doc) => set(doc, 'attributes.mynum', doc.attributes.mynum * 2), + }, }, - }; + ]; await createIndex({ esClient, index }); await createDocs({ esClient, index, docs: originalDocs }); @@ -107,7 +134,7 @@ export default ({ getService }: FtrProviderContext) => { const result = await migrateIndex({ esClient, index, - migrations, + savedObjectTypes, mappingProperties, obsoleteIndexTemplatePattern: 'migration_a*', }); @@ -129,13 +156,7 @@ export default ({ getService }: FtrProviderContext) => { }); // The docs in the original index are unchanged - expect(await fetchDocs(esClient, `${index}_1`)).to.eql([ - { id: 'bar:i', type: 'bar', bar: { nomnom: 33 } }, - { id: 'bar:o', type: 'bar', bar: { nomnom: 2 } }, - { id: 'baz:u', type: 'baz', baz: { title: 'Terrific!' } }, - { id: 'foo:a', type: 'foo', foo: { name: 'Foo A' } }, - { id: 'foo:e', type: 'foo', foo: { name: 'Fooey' } }, - ]); + expect(await fetchDocs(esClient, `${index}_1`)).to.eql(originalDocs.sort(sortByTypeAndId)); // The docs in the alias have been migrated expect(await fetchDocs(esClient, index)).to.eql([ @@ -145,6 +166,7 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { bar: '1.9.0' }, bar: { mynum: 68 }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, { id: 'bar:o', @@ -152,14 +174,22 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { bar: '1.9.0' }, bar: { mynum: 6 }, references: [], + coreMigrationVersion: KIBANA_VERSION, + }, + { + id: 'baz:u', + type: 'baz', + baz: { title: 'Terrific!' }, + references: [], + coreMigrationVersion: KIBANA_VERSION, }, - { id: 'baz:u', type: 'baz', baz: { title: 'Terrific!' }, references: [] }, { id: 'foo:a', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOO A' }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, { id: 'foo:e', @@ -167,6 +197,7 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOOEY' }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, ]); }); @@ -185,28 +216,46 @@ export default ({ getService }: FtrProviderContext) => { bar: { properties: { mynum: { type: 'integer' } } }, }; - const migrations: Record = { - foo: { - '1.0.0': (doc) => set(doc, 'attributes.name', doc.attributes.name.toUpperCase()), + let savedObjectTypes: SavedObjectsType[] = [ + { + ...FOO_TYPE, + migrations: { + '1.0.0': (doc) => set(doc, 'attributes.name', doc.attributes.name.toUpperCase()), + }, }, - bar: { - '1.0.0': (doc) => set(doc, 'attributes.nomnom', doc.attributes.nomnom + 1), - '1.3.0': (doc) => set(doc, 'attributes', { mynum: doc.attributes.nomnom }), - '1.9.0': (doc) => set(doc, 'attributes.mynum', doc.attributes.mynum * 2), + { + ...BAR_TYPE, + migrations: { + '1.0.0': (doc) => set(doc, 'attributes.nomnom', doc.attributes.nomnom + 1), + '1.3.0': (doc) => set(doc, 'attributes', { mynum: doc.attributes.nomnom }), + '1.9.0': (doc) => set(doc, 'attributes.mynum', doc.attributes.mynum * 2), + }, }, - }; + ]; await createIndex({ esClient, index }); await createDocs({ esClient, index, docs: originalDocs }); - await migrateIndex({ esClient, index, migrations, mappingProperties }); + await migrateIndex({ esClient, index, savedObjectTypes, mappingProperties }); // @ts-expect-error name doesn't exist on mynum type mappingProperties.bar.properties.name = { type: 'keyword' }; - migrations.foo['2.0.1'] = (doc) => set(doc, 'attributes.name', `${doc.attributes.name}v2`); - migrations.bar['2.3.4'] = (doc) => set(doc, 'attributes.name', `NAME ${doc.id}`); + savedObjectTypes = [ + { + ...FOO_TYPE, + migrations: { + '2.0.1': (doc) => set(doc, 'attributes.name', `${doc.attributes.name}v2`), + }, + }, + { + ...BAR_TYPE, + migrations: { + '2.3.4': (doc) => set(doc, 'attributes.name', `NAME ${doc.id}`), + }, + }, + ]; - await migrateIndex({ esClient, index, migrations, mappingProperties }); + await migrateIndex({ esClient, index, savedObjectTypes, mappingProperties }); // The index for the initial migration has not been destroyed... expect(await fetchDocs(esClient, `${index}_2`)).to.eql([ @@ -216,6 +265,7 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { bar: '1.9.0' }, bar: { mynum: 68 }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, { id: 'bar:o', @@ -223,6 +273,7 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { bar: '1.9.0' }, bar: { mynum: 6 }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, { id: 'foo:a', @@ -230,6 +281,7 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOO A' }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, { id: 'foo:e', @@ -237,6 +289,7 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOOEY' }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, ]); @@ -248,6 +301,7 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { bar: '2.3.4' }, bar: { mynum: 68, name: 'NAME i' }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, { id: 'bar:o', @@ -255,6 +309,7 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { bar: '2.3.4' }, bar: { mynum: 6, name: 'NAME o' }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, { id: 'foo:a', @@ -262,6 +317,7 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { foo: '2.0.1' }, foo: { name: 'FOO Av2' }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, { id: 'foo:e', @@ -269,6 +325,7 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { foo: '2.0.1' }, foo: { name: 'FOOEYv2' }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, ]); }); @@ -281,18 +338,21 @@ export default ({ getService }: FtrProviderContext) => { foo: { properties: { name: { type: 'text' } } }, }; - const migrations: Record = { - foo: { - '1.0.0': (doc) => set(doc, 'attributes.name', 'LOTR'), + const savedObjectTypes: SavedObjectsType[] = [ + { + ...FOO_TYPE, + migrations: { + '1.0.0': (doc) => set(doc, 'attributes.name', 'LOTR'), + }, }, - }; + ]; await createIndex({ esClient, index }); await createDocs({ esClient, index, docs: originalDocs }); const result = await Promise.all([ - migrateIndex({ esClient, index, migrations, mappingProperties }), - migrateIndex({ esClient, index, migrations, mappingProperties }), + migrateIndex({ esClient, index, savedObjectTypes, mappingProperties }), + migrateIndex({ esClient, index, savedObjectTypes, mappingProperties }), ]); // The polling instance and the migrating instance should both @@ -327,9 +387,170 @@ export default ({ getService }: FtrProviderContext) => { migrationVersion: { foo: '1.0.0' }, foo: { name: 'LOTR' }, references: [], + coreMigrationVersion: KIBANA_VERSION, }, ]); }); + + it('Correctly applies reference transforms and conversion transforms', async () => { + const index = '.migration-d'; + const originalDocs = [ + { id: 'foo:1', type: 'foo', foo: { name: 'Foo 1 default' } }, + { id: 'spacex:foo:1', type: 'foo', foo: { name: 'Foo 1 spacex' }, namespace: 'spacex' }, + { + id: 'bar:1', + type: 'bar', + bar: { nomnom: 1 }, + references: [{ type: 'foo', id: '1', name: 'Foo 1 default' }], + }, + { + id: 'spacex:bar:1', + type: 'bar', + bar: { nomnom: 2 }, + references: [{ type: 'foo', id: '1', name: 'Foo 1 spacex' }], + namespace: 'spacex', + }, + { + id: 'baz:1', + type: 'baz', + baz: { title: 'Baz 1 default' }, + references: [{ type: 'bar', id: '1', name: 'Bar 1 default' }], + }, + { + id: 'spacex:baz:1', + type: 'baz', + baz: { title: 'Baz 1 spacex' }, + references: [{ type: 'bar', id: '1', name: 'Bar 1 spacex' }], + namespace: 'spacex', + }, + ]; + + const mappingProperties = { + foo: { properties: { name: { type: 'text' } } }, + bar: { properties: { nomnom: { type: 'integer' } } }, + baz: { properties: { title: { type: 'keyword' } } }, + }; + + const savedObjectTypes: SavedObjectsType[] = [ + { + ...FOO_TYPE, + namespaceType: 'multiple', + convertToMultiNamespaceTypeVersion: '1.0.0', + }, + { + ...BAR_TYPE, + namespaceType: 'multiple', + convertToMultiNamespaceTypeVersion: '2.0.0', + }, + BAZ_TYPE, // must be registered for reference transforms to be applied to objects of this type + ]; + + await createIndex({ esClient, index }); + await createDocs({ esClient, index, docs: originalDocs }); + + await migrateIndex({ + esClient, + index, + savedObjectTypes, + mappingProperties, + obsoleteIndexTemplatePattern: 'migration_a*', + }); + + // The docs in the original index are unchanged + expect(await fetchDocs(esClient, `${index}_1`)).to.eql(originalDocs.sort(sortByTypeAndId)); + + // The docs in the alias have been migrated + const migratedDocs = await fetchDocs(esClient, index); + + // each newly converted multi-namespace object in a non-default space has its ID deterministically regenerated, and a legacy-url-alias + // object is created which links the old ID to the new ID + const newFooId = uuidv5('spacex:foo:1', uuidv5.DNS); + const newBarId = uuidv5('spacex:bar:1', uuidv5.DNS); + + expect(migratedDocs).to.eql( + [ + { + id: 'foo:1', + type: 'foo', + foo: { name: 'Foo 1 default' }, + references: [], + namespaces: ['default'], + migrationVersion: { foo: '1.0.0' }, + coreMigrationVersion: KIBANA_VERSION, + }, + { + id: `foo:${newFooId}`, + type: 'foo', + foo: { name: 'Foo 1 spacex' }, + references: [], + namespaces: ['spacex'], + originId: '1', + migrationVersion: { foo: '1.0.0' }, + coreMigrationVersion: KIBANA_VERSION, + }, + { + // new object + id: 'legacy-url-alias:spacex:foo:1', + type: 'legacy-url-alias', + 'legacy-url-alias': { + targetId: newFooId, + targetNamespace: 'spacex', + targetType: 'foo', + }, + migrationVersion: {}, + references: [], + coreMigrationVersion: KIBANA_VERSION, + }, + { + id: 'bar:1', + type: 'bar', + bar: { nomnom: 1 }, + references: [{ type: 'foo', id: '1', name: 'Foo 1 default' }], + namespaces: ['default'], + migrationVersion: { bar: '2.0.0' }, + coreMigrationVersion: KIBANA_VERSION, + }, + { + id: `bar:${newBarId}`, + type: 'bar', + bar: { nomnom: 2 }, + references: [{ type: 'foo', id: newFooId, name: 'Foo 1 spacex' }], + namespaces: ['spacex'], + originId: '1', + migrationVersion: { bar: '2.0.0' }, + coreMigrationVersion: KIBANA_VERSION, + }, + { + // new object + id: 'legacy-url-alias:spacex:bar:1', + type: 'legacy-url-alias', + 'legacy-url-alias': { + targetId: newBarId, + targetNamespace: 'spacex', + targetType: 'bar', + }, + migrationVersion: {}, + references: [], + coreMigrationVersion: KIBANA_VERSION, + }, + { + id: 'baz:1', + type: 'baz', + baz: { title: 'Baz 1 default' }, + references: [{ type: 'bar', id: '1', name: 'Bar 1 default' }], + coreMigrationVersion: KIBANA_VERSION, + }, + { + id: 'spacex:baz:1', + type: 'baz', + baz: { title: 'Baz 1 spacex' }, + references: [{ type: 'bar', id: newBarId, name: 'Bar 1 spacex' }], + namespace: 'spacex', + coreMigrationVersion: KIBANA_VERSION, + }, + ].sort(sortByTypeAndId) + ); + }); }); }; @@ -340,6 +561,30 @@ async function createIndex({ esClient, index }: { esClient: ElasticsearchClient; foo: { properties: { name: { type: 'keyword' } } }, bar: { properties: { nomnom: { type: 'integer' } } }, baz: { properties: { title: { type: 'keyword' } } }, + 'legacy-url-alias': { + properties: { + targetNamespace: { type: 'text' }, + targetType: { type: 'text' }, + targetId: { type: 'text' }, + lastResolved: { type: 'date' }, + resolveCounter: { type: 'integer' }, + disabled: { type: 'boolean' }, + }, + }, + namespace: { type: 'keyword' }, + namespaces: { type: 'keyword' }, + originId: { type: 'keyword' }, + references: { + type: 'nested', + properties: { + name: { type: 'keyword' }, + type: { type: 'keyword' }, + id: { type: 'keyword' }, + }, + }, + coreMigrationVersion: { + type: 'keyword', + }, }; await esClient.indices.create({ index, @@ -369,23 +614,23 @@ async function createDocs({ async function migrateIndex({ esClient, index, - migrations, + savedObjectTypes, mappingProperties, obsoleteIndexTemplatePattern, }: { esClient: ElasticsearchClient; index: string; - migrations: Record; + savedObjectTypes: SavedObjectsType[]; mappingProperties: SavedObjectsTypeMappingDefinitions; obsoleteIndexTemplatePattern?: string; }) { const typeRegistry = new SavedObjectTypeRegistry(); - const types = migrationsToTypes(migrations); - types.forEach((type) => typeRegistry.registerType(type)); + savedObjectTypes.forEach((type) => typeRegistry.registerType(type)); const documentMigrator = new DocumentMigrator({ - kibanaVersion: '99.9.9', + kibanaVersion: KIBANA_VERSION, typeRegistry, + minimumConvertVersion: '0.0.0', // bypass the restriction of a minimum version of 8.0.0 for these integration tests log: getLogMock(), }); @@ -395,6 +640,7 @@ async function migrateIndex({ client: createMigrationEsClient(esClient, getLogMock()), documentMigrator, index, + kibanaVersion: KIBANA_VERSION, obsoleteIndexTemplatePattern, mappingProperties, batchSize: 10, @@ -407,18 +653,6 @@ async function migrateIndex({ return await migrator.migrate(); } -function migrationsToTypes( - migrations: Record -): SavedObjectsType[] { - return Object.entries(migrations).map(([type, migrationsMap]) => ({ - name: type, - hidden: false, - namespaceType: 'single', - mappings: { properties: {} }, - migrations: { ...migrationsMap }, - })); -} - async function fetchDocs(esClient: ElasticsearchClient, index: string) { const { body } = await esClient.search>({ index }); @@ -427,5 +661,9 @@ async function fetchDocs(esClient: ElasticsearchClient, index: string) { ...h._source, id: h._id, })) - .sort((a, b) => a.id.localeCompare(b.id)); + .sort(sortByTypeAndId); +} + +function sortByTypeAndId(a: { type: string; id: string }, b: { type: string; id: string }) { + return a.type.localeCompare(b.type) || a.id.localeCompare(b.id); } diff --git a/test/api_integration/apis/saved_objects/resolve.ts b/test/api_integration/apis/saved_objects/resolve.ts new file mode 100644 index 0000000000000..b71d5e3003495 --- /dev/null +++ b/test/api_integration/apis/saved_objects/resolve.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../../ftr_provider_context'; +import { getKibanaVersion } from './lib/saved_objects_test_utils'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('legacyEs'); + const esArchiver = getService('esArchiver'); + + describe('resolve', () => { + let KIBANA_VERSION: string; + + before(async () => { + KIBANA_VERSION = await getKibanaVersion(getService); + }); + + describe('with kibana index', () => { + before(() => esArchiver.load('saved_objects/basic')); + after(() => esArchiver.unload('saved_objects/basic')); + + it('should return 200', async () => + await supertest + .get(`/api/saved_objects/resolve/visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab`) + .expect(200) + .then((resp) => { + expect(resp.body).to.eql({ + saved_object: { + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + type: 'visualization', + updated_at: '2017-09-21T18:51:23.794Z', + version: resp.body.saved_object.version, + migrationVersion: resp.body.saved_object.migrationVersion, + coreMigrationVersion: KIBANA_VERSION, + attributes: { + title: 'Count of requests', + description: '', + version: 1, + // cheat for some of the more complex attributes + visState: resp.body.saved_object.attributes.visState, + uiStateJSON: resp.body.saved_object.attributes.uiStateJSON, + kibanaSavedObjectMeta: resp.body.saved_object.attributes.kibanaSavedObjectMeta, + }, + references: [ + { + type: 'index-pattern', + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + id: '91200a00-9efd-11e7-acb3-3dab96693fab', + }, + ], + namespaces: ['default'], + }, + outcome: 'exactMatch', + }); + expect(resp.body.saved_object.migrationVersion).to.be.ok(); + })); + + describe('doc does not exist', () => { + it('should return same generic error as when index does not exist', async () => + await supertest + .get(`/api/saved_objects/resolve/visualization/foobar`) + .expect(404) + .then((resp) => { + expect(resp.body).to.eql({ + error: 'Not Found', + message: 'Saved object [visualization/foobar] not found', + statusCode: 404, + }); + })); + }); + }); + + describe('without kibana index', () => { + before( + async () => + // just in case the kibana server has recreated it + await es.indices.delete({ + index: '.kibana', + ignore: [404], + }) + ); + + it('should return basic 404 without mentioning index', async () => + await supertest + .get('/api/saved_objects/resolve/visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab') + .expect(404) + .then((resp) => { + expect(resp.body).to.eql({ + error: 'Not Found', + message: + 'Saved object [visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab] not found', + statusCode: 404, + }); + })); + }); + }); +} diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index d7b486e8ab5cf..acc01c73de674 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -14,8 +14,17 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('es'); const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); describe('find', () => { + let KIBANA_VERSION: string; + + before(async () => { + KIBANA_VERSION = await kibanaServer.version.get(); + expect(typeof KIBANA_VERSION).to.eql('string'); + expect(KIBANA_VERSION.length).to.be.greaterThan(0); + }); + describe('with kibana index', () => { before(() => esArchiver.load('saved_objects/basic')); after(() => esArchiver.unload('saved_objects/basic')); @@ -38,6 +47,7 @@ export default function ({ getService }: FtrProviderContext) { title: 'Count of requests', }, migrationVersion: resp.body.saved_objects[0].migrationVersion, + coreMigrationVersion: KIBANA_VERSION, namespaces: ['default'], references: [ { diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts index 85ec08fb7388d..90700f8fa7521 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts @@ -1452,6 +1452,140 @@ describe('#get', () => { }); }); +describe('#resolve', () => { + it('redirects request to underlying base client and does not alter response if type is not registered', async () => { + const mockedResponse = { + saved_object: { + id: 'some-id', + type: 'unknown-type', + attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, + references: [], + }, + outcome: 'exactMatch' as 'exactMatch', + }; + + mockBaseClient.resolve.mockResolvedValue(mockedResponse); + + const options = { namespace: 'some-ns' }; + await expect(wrapper.resolve('unknown-type', 'some-id', options)).resolves.toEqual( + mockedResponse + ); + expect(mockBaseClient.resolve).toHaveBeenCalledTimes(1); + expect(mockBaseClient.resolve).toHaveBeenCalledWith('unknown-type', 'some-id', options); + }); + + it('redirects request to underlying base client and strips encrypted attributes except for ones with `dangerouslyExposeValue` set to `true` if type is registered', async () => { + const mockedResponse = { + saved_object: { + id: 'some-id', + type: 'known-type', + attributes: { + attrOne: 'one', + attrSecret: '*secret*', + attrNotSoSecret: '*not-so-secret*', + attrThree: 'three', + }, + references: [], + }, + outcome: 'exactMatch' as 'exactMatch', + }; + + mockBaseClient.resolve.mockResolvedValue(mockedResponse); + + const options = { namespace: 'some-ns' }; + await expect(wrapper.resolve('known-type', 'some-id', options)).resolves.toEqual({ + ...mockedResponse, + saved_object: { + ...mockedResponse.saved_object, + attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, + }, + }); + expect(mockBaseClient.resolve).toHaveBeenCalledTimes(1); + expect(mockBaseClient.resolve).toHaveBeenCalledWith('known-type', 'some-id', options); + + expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( + 1 + ); + expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( + { type: 'known-type', id: 'some-id', namespace: 'some-ns' }, + { + attrOne: 'one', + attrSecret: '*secret*', + attrNotSoSecret: '*not-so-secret*', + attrThree: 'three', + }, + undefined, + { user: mockAuthenticatedUser() } + ); + }); + + it('includes both attributes and error with modified outcome if decryption fails.', async () => { + const mockedResponse = { + saved_object: { + id: 'some-id', + type: 'known-type', + attributes: { + attrOne: 'one', + attrSecret: '*secret*', + attrNotSoSecret: '*not-so-secret*', + attrThree: 'three', + }, + references: [], + }, + outcome: 'exactMatch' as 'exactMatch', + }; + + mockBaseClient.resolve.mockResolvedValue(mockedResponse); + + const decryptionError = new EncryptionError( + 'something failed', + 'attrNotSoSecret', + EncryptionErrorOperation.Decryption + ); + encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes.mockResolvedValue({ + attributes: { attrOne: 'one', attrThree: 'three' }, + error: decryptionError, + }); + + const options = { namespace: 'some-ns' }; + await expect(wrapper.resolve('known-type', 'some-id', options)).resolves.toEqual({ + ...mockedResponse, + saved_object: { + ...mockedResponse.saved_object, + attributes: { attrOne: 'one', attrThree: 'three' }, + error: decryptionError, + }, + }); + expect(mockBaseClient.resolve).toHaveBeenCalledTimes(1); + expect(mockBaseClient.resolve).toHaveBeenCalledWith('known-type', 'some-id', options); + + expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( + 1 + ); + expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( + { type: 'known-type', id: 'some-id', namespace: 'some-ns' }, + { + attrOne: 'one', + attrSecret: '*secret*', + attrNotSoSecret: '*not-so-secret*', + attrThree: 'three', + }, + undefined, + { user: mockAuthenticatedUser() } + ); + }); + + it('fails if base client fails', async () => { + const failureReason = new Error('Something bad happened...'); + mockBaseClient.resolve.mockRejectedValue(failureReason); + + await expect(wrapper.resolve('known-type', 'some-id')).rejects.toThrowError(failureReason); + + expect(mockBaseClient.resolve).toHaveBeenCalledTimes(1); + expect(mockBaseClient.resolve).toHaveBeenCalledWith('known-type', 'some-id', undefined); + }); +}); + describe('#update', () => { it('redirects request to underlying base client if type is not registered', async () => { const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts index 313e7c7da9eba..c3008a8e86505 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts @@ -181,6 +181,19 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon ); } + public async resolve(type: string, id: string, options?: SavedObjectsBaseOptions) { + const resolveResult = await this.options.baseClient.resolve(type, id, options); + const object = await this.handleEncryptedAttributesInResponse( + resolveResult.saved_object, + undefined as unknown, + getDescriptorNamespace(this.options.baseTypeRegistry, type, options?.namespace) + ); + return { + ...resolveResult, + saved_object: object, + }; + } + public async update( type: string, id: string, diff --git a/x-pack/plugins/security/server/audit/audit_events.test.ts b/x-pack/plugins/security/server/audit/audit_events.test.ts index f7e41bce674ee..ef77170de69e2 100644 --- a/x-pack/plugins/security/server/audit/audit_events.test.ts +++ b/x-pack/plugins/security/server/audit/audit_events.test.ts @@ -115,6 +115,12 @@ describe('#savedObjectEvent', () => { savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' }, }) ).not.toBeUndefined(); + expect( + savedObjectEvent({ + action: SavedObjectAction.RESOLVE, + savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' }, + }) + ).not.toBeUndefined(); expect( savedObjectEvent({ action: SavedObjectAction.FIND, @@ -136,6 +142,18 @@ describe('#savedObjectEvent', () => { savedObject: { type: 'telemetry', id: 'SAVED_OBJECT_ID' }, }) ).toBeUndefined(); + expect( + savedObjectEvent({ + action: SavedObjectAction.RESOLVE, + savedObject: { type: 'config', id: 'SAVED_OBJECT_ID' }, + }) + ).toBeUndefined(); + expect( + savedObjectEvent({ + action: SavedObjectAction.RESOLVE, + savedObject: { type: 'telemetry', id: 'SAVED_OBJECT_ID' }, + }) + ).toBeUndefined(); expect( savedObjectEvent({ action: SavedObjectAction.FIND, diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index b6538af31bd60..f7d99877bca27 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -182,6 +182,7 @@ export function userLoginEvent({ export enum SavedObjectAction { CREATE = 'saved_object_create', GET = 'saved_object_get', + RESOLVE = 'saved_object_resolve', UPDATE = 'saved_object_update', DELETE = 'saved_object_delete', FIND = 'saved_object_find', @@ -195,6 +196,7 @@ type VerbsTuple = [string, string, string]; const savedObjectAuditVerbs: Record = { saved_object_create: ['create', 'creating', 'created'], saved_object_get: ['access', 'accessing', 'accessed'], + saved_object_resolve: ['resolve', 'resolving', 'resolved'], saved_object_update: ['update', 'updating', 'updated'], saved_object_delete: ['delete', 'deleting', 'deleted'], saved_object_find: ['access', 'accessing', 'accessed'], @@ -210,6 +212,7 @@ const savedObjectAuditVerbs: Record = { const savedObjectAuditTypes: Record = { saved_object_create: EventType.CREATION, saved_object_get: EventType.ACCESS, + saved_object_resolve: EventType.ACCESS, saved_object_update: EventType.CHANGE, saved_object_delete: EventType.DELETION, saved_object_find: EventType.ACCESS, diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index 15ca8bac89bd6..5c421776d54f0 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -175,6 +175,7 @@ const expectObjectNamespaceFiltering = async ( // we don't know which base client method will be called; mock them all clientOpts.baseClient.create.mockReturnValue(returnValue as any); clientOpts.baseClient.get.mockReturnValue(returnValue as any); + // 'resolve' is excluded because it has a specific test case written for it clientOpts.baseClient.update.mockReturnValue(returnValue as any); clientOpts.baseClient.addToNamespaces.mockReturnValue(returnValue as any); clientOpts.baseClient.deleteFromNamespaces.mockReturnValue(returnValue as any); @@ -985,6 +986,82 @@ describe('#get', () => { }); }); +describe('#resolve', () => { + const type = 'foo'; + const id = `${type}-id`; + const namespace = 'some-ns'; + const resolvedId = 'another-id'; // success audit records include the resolved ID, not the requested ID + const mockResult = { saved_object: { id: resolvedId } }; // mock result needs to have ID for audit logging + + test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { + await expectGeneralError(client.resolve, { type, id }); + }); + + test(`throws decorated ForbiddenError when unauthorized`, async () => { + const options = { namespace }; + await expectForbiddenError(client.resolve, { type, id, options }, 'resolve'); + }); + + test(`returns result of baseClient.resolve when authorized`, async () => { + const apiCallReturnValue = mockResult; + clientOpts.baseClient.resolve.mockReturnValue(apiCallReturnValue as any); + + const options = { namespace }; + const result = await expectSuccess(client.resolve, { type, id, options }, 'resolve'); + expect(result).toEqual(apiCallReturnValue); + }); + + test(`checks privileges for user, actions, and namespace`, async () => { + const options = { namespace }; + await expectPrivilegeCheck(client.resolve, { type, id, options }, namespace); + }); + + test(`filters namespaces that the user doesn't have access to`, async () => { + const options = { namespace }; + + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementationOnce( + getMockCheckPrivilegesSuccess // privilege check for authorization + ); + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( + getMockCheckPrivilegesFailure // privilege check for namespace filtering + ); + + const namespaces = ['some-other-namespace', '*', namespace]; + const returnValue = { saved_object: { namespaces, id: resolvedId, foo: 'bar' } }; + clientOpts.baseClient.resolve.mockReturnValue(returnValue as any); + + const result = await client.resolve(type, id, options); + // we will never redact the "All Spaces" ID + expect(result).toEqual({ + saved_object: expect.objectContaining({ namespaces: ['*', namespace, '?'] }), + }); + + expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(2); + expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenLastCalledWith( + 'login:', + ['some-other-namespace'] + // when we check what namespaces to redact, we don't check privileges for '*', only actual space IDs + // we don't check privileges for authorizedNamespace either, as that was already checked earlier in the operation + ); + }); + + test(`adds audit event when successful`, async () => { + const apiCallReturnValue = mockResult; + clientOpts.baseClient.resolve.mockReturnValue(apiCallReturnValue as any); + const options = { namespace }; + await expectSuccess(client.resolve, { type, id, options }, 'resolve'); + expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); + expectAuditEvent('saved_object_resolve', EventOutcome.SUCCESS, { type, id: resolvedId }); + }); + + test(`adds audit event when not successful`, async () => { + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); + await expect(() => client.resolve(type, id, { namespace })).rejects.toThrow(); + expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); + expectAuditEvent('saved_object_resolve', EventOutcome.FAILURE, { type, id }); + }); +}); + describe('#deleteFromNamespaces', () => { const type = 'foo'; const id = `${type}-id`; diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index 765274a839efa..e53bb742e2179 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -335,6 +335,42 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra return await this.redactSavedObjectNamespaces(savedObject, [options.namespace]); } + public async resolve( + type: string, + id: string, + options: SavedObjectsBaseOptions = {} + ) { + try { + const args = { type, id, options }; + await this.ensureAuthorized(type, 'get', options.namespace, { args, auditAction: 'resolve' }); + } catch (error) { + this.auditLogger.log( + savedObjectEvent({ + action: SavedObjectAction.RESOLVE, + savedObject: { type, id }, + error, + }) + ); + throw error; + } + + const resolveResult = await this.baseClient.resolve(type, id, options); + + this.auditLogger.log( + savedObjectEvent({ + action: SavedObjectAction.RESOLVE, + savedObject: { type, id: resolveResult.saved_object.id }, + }) + ); + + return { + ...resolveResult, + saved_object: await this.redactSavedObjectNamespaces(resolveResult.saved_object, [ + options.namespace, + ]), + }; + } + public async update( type: string, id: string, diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index 4fd9529507335..a79651c1ae9a6 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -103,6 +103,37 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; }); }); + describe('#resolve', () => { + test(`throws error if options.namespace is specified`, async () => { + const { client } = createSpacesSavedObjectsClient(); + + await expect(client.resolve('foo', '', { namespace: 'bar' })).rejects.toThrow( + ERROR_NAMESPACE_SPECIFIED + ); + }); + + test(`supplements options with the current namespace`, async () => { + const { client, baseClient } = createSpacesSavedObjectsClient(); + const expectedReturnValue = { + saved_object: createMockResponse(), + outcome: 'exactMatch' as 'exactMatch', // outcome doesn't matter, just including it for type safety + }; + baseClient.resolve.mockReturnValue(Promise.resolve(expectedReturnValue)); + + const type = Symbol(); + const id = Symbol(); + const options = Object.freeze({ foo: 'bar' }); + // @ts-expect-error + const actualReturnValue = await client.resolve(type, id, options); + + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.resolve).toHaveBeenCalledWith(type, id, { + foo: 'bar', + namespace: currentSpace.expectedNamespace, + }); + }); + }); + describe('#bulkGet', () => { test(`throws error if options.namespace is specified`, async () => { const { client } = createSpacesSavedObjectsClient(); diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts index 049bd88085ed5..bd09b8237a468 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts @@ -246,6 +246,28 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } + /** + * Resolves a single object, using any legacy URL alias if it exists + * + * @param type - The type of SavedObject to retrieve + * @param id - The ID of the SavedObject to retrieve + * @param {object} [options={}] + * @property {string} [options.namespace] + * @returns {promise} - { saved_object, outcome } + */ + public async resolve( + type: string, + id: string, + options: SavedObjectsBaseOptions = {} + ) { + throwErrorIfNamespaceSpecified(options); + + return await this.client.resolve(type, id, { + ...options, + namespace: spaceIdToNamespace(this.spaceId), + }); + } + /** * Updates an object * diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index d9d5c6f9c5808..32cae675dea74 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -502,3 +502,119 @@ "type": "doc" } } + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "resolvetype:exact-match", + "source": { + "type": "resolvetype", + "updated_at": "2017-09-21T18:51:23.794Z", + "resolvetype": { + "title": "Resolve outcome exactMatch" + }, + "namespaces": ["default", "space_1", "space_2"] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "resolvetype:alias-match-newid", + "source": { + "type": "resolvetype", + "updated_at": "2017-09-21T18:51:23.794Z", + "resolvetype": { + "title": "Resolve outcome aliasMatch" + }, + "namespaces": ["default", "space_1", "space_2"] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "legacy-url-alias:space_1:resolvetype:alias-match", + "source": { + "type": "legacy-url-alias", + "updated_at": "2017-09-21T18:51:23.794Z", + "legacy-url-alias": { + "targetNamespace": "space_1", + "targetType": "resolvetype", + "targetId": "alias-match-newid" + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "legacy-url-alias:space_1:resolvetype:disabled", + "source": { + "type": "legacy-url-alias", + "updated_at": "2017-09-21T18:51:23.794Z", + "legacy-url-alias": { + "targetNamespace": "space_1", + "targetType": "resolvetype", + "targetId": "alias-match-newid", + "disabled": true + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "resolvetype:conflict", + "source": { + "type": "resolvetype", + "updated_at": "2017-09-21T18:51:23.794Z", + "resolvetype": { + "title": "Resolve outcome conflict (1 of 2)" + }, + "namespaces": ["default", "space_1", "space_2"] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "resolvetype:conflict-newid", + "source": { + "type": "resolvetype", + "updated_at": "2017-09-21T18:51:23.794Z", + "resolvetype": { + "title": "Resolve outcome conflict (2 of 2)" + }, + "namespaces": ["default", "space_1", "space_2"] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "legacy-url-alias:space_1:resolvetype:conflict", + "source": { + "type": "legacy-url-alias", + "updated_at": "2017-09-21T18:51:23.794Z", + "legacy-url-alias": { + "targetNamespace": "space_1", + "targetType": "resolvetype", + "targetId": "conflict-newid" + } + } + } +} diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json index 73f0e536b9295..561c2ecc56fa2 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json +++ b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json @@ -176,6 +176,28 @@ } } }, + "legacy-url-alias": { + "properties": { + "targetNamespace": { + "type": "keyword" + }, + "targetType": { + "type": "keyword" + }, + "targetId": { + "type": "keyword" + }, + "lastResolved": { + "type": "date" + }, + "resolveCounter": { + "type": "integer" + }, + "disabled": { + "type": "boolean" + } + } + }, "namespace": { "type": "keyword" }, @@ -185,6 +207,13 @@ "originId": { "type": "keyword" }, + "resolvetype": { + "properties": { + "title": { + "type": "text" + } + } + }, "search": { "properties": { "columns": { diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/saved_object_test_plugin/server/plugin.ts b/x-pack/test/saved_object_api_integration/common/fixtures/saved_object_test_plugin/server/plugin.ts index 45880635586a7..d311e539b1687 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/saved_object_test_plugin/server/plugin.ts +++ b/x-pack/test/saved_object_api_integration/common/fixtures/saved_object_test_plugin/server/plugin.ts @@ -64,6 +64,13 @@ export class Plugin { namespaceType: 'single', mappings, }); + core.savedObjects.registerType({ + name: 'resolvetype', + hidden: false, + namespaceType: 'multiple', + management, + mappings, + }); } public start() { diff --git a/x-pack/test/saved_object_api_integration/common/suites/resolve.ts b/x-pack/test/saved_object_api_integration/common/suites/resolve.ts new file mode 100644 index 0000000000000..250a3b19710a9 --- /dev/null +++ b/x-pack/test/saved_object_api_integration/common/suites/resolve.ts @@ -0,0 +1,138 @@ +/* + * 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 { SuperTest } from 'supertest'; +import { SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; +import { SPACES } from '../lib/spaces'; +import { + createRequest, + expectResponses, + getUrlPrefix, + getTestTitle, +} from '../lib/saved_object_test_utils'; +import { ExpectResponseBody, TestCase, TestDefinition, TestSuite } from '../lib/types'; + +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; + +export interface ResolveTestDefinition extends TestDefinition { + request: { type: string; id: string }; +} +export type ResolveTestSuite = TestSuite; +export interface ResolveTestCase extends TestCase { + expectedOutcome?: 'exactMatch' | 'aliasMatch' | 'conflict'; + expectedId?: string; +} + +const EACH_SPACE = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]; + +export const TEST_CASES = Object.freeze({ + EXACT_MATCH: Object.freeze({ + type: 'resolvetype', + id: 'exact-match', + expectedNamespaces: EACH_SPACE, + expectedOutcome: 'exactMatch' as 'exactMatch', + expectedId: 'exact-match', + }), + ALIAS_MATCH: Object.freeze({ + type: 'resolvetype', + id: 'alias-match', + expectedNamespaces: EACH_SPACE, + expectedOutcome: 'aliasMatch' as 'aliasMatch', + expectedId: 'alias-match-newid', + }), + CONFLICT: Object.freeze({ + type: 'resolvetype', + id: 'conflict', + expectedNamespaces: EACH_SPACE, + expectedOutcome: 'conflict' as 'conflict', // only in space 1, where the alias exists + expectedId: 'conflict', + }), + DISABLED: Object.freeze({ + type: 'resolvetype', + id: 'disabled', + }), + DOES_NOT_EXIST: Object.freeze({ + type: 'resolvetype', + id: 'does-not-exist', + }), + HIDDEN: CASES.HIDDEN, +}); + +export function resolveTestSuiteFactory(esArchiver: any, supertest: SuperTest) { + const expectSavedObjectForbidden = expectResponses.forbiddenTypes('get'); + const expectResponseBody = (testCase: ResolveTestCase): ExpectResponseBody => async ( + response: Record + ) => { + if (testCase.failure === 403) { + await expectSavedObjectForbidden(testCase.type)(response); + } else { + // permitted + const object = response.body.saved_object || response.body; // errors do not have a saved_object field + const { expectedId: id, expectedOutcome } = testCase; + await expectResponses.permitted(object, { ...testCase, ...(id && { id }) }); + if (expectedOutcome && !testCase.failure) { + expect(response.body.outcome).to.eql(expectedOutcome); + } + } + }; + const createTestDefinitions = ( + testCases: ResolveTestCase | ResolveTestCase[], + forbidden: boolean, + options?: { + spaceId?: string; + responseBodyOverride?: ExpectResponseBody; + } + ): ResolveTestDefinition[] => { + let cases = Array.isArray(testCases) ? testCases : [testCases]; + if (forbidden) { + // override the expected result in each test case + cases = cases.map((x) => ({ ...x, failure: 403 })); + } + return cases.map((x) => ({ + title: getTestTitle(x), + responseStatusCode: x.failure ?? 200, + request: createRequest(x), + responseBody: options?.responseBodyOverride || expectResponseBody(x), + })); + }; + + const makeResolveTest = (describeFn: Mocha.SuiteFunction) => ( + description: string, + definition: ResolveTestSuite + ) => { + const { user, spaceId = SPACES.DEFAULT.spaceId, tests } = definition; + + describeFn(description, () => { + before(() => esArchiver.load('saved_objects/spaces')); + after(() => esArchiver.unload('saved_objects/spaces')); + + for (const test of tests) { + it(`should return ${test.responseStatusCode} ${test.title}`, async () => { + const { type, id } = test.request; + await supertest + .get(`${getUrlPrefix(spaceId)}/api/saved_objects/resolve/${type}/${id}`) + .auth(user?.username, user?.password) + .expect(test.responseStatusCode) + .then(test.responseBody); + }); + } + }); + }; + + const addTests = makeResolveTest(describe); + // @ts-ignore + addTests.only = makeResolveTest(describe.only); + + return { + addTests, + createTestDefinitions, + }; +} diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/index.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/index.ts index 3cc6b85cb97c0..5e9e499ffea18 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/index.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/index.ts @@ -28,6 +28,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./import')); loadTestFile(require.resolve('./resolve_import_errors')); + loadTestFile(require.resolve('./resolve')); loadTestFile(require.resolve('./update')); }); } diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve.ts new file mode 100644 index 0000000000000..94df364c9017c --- /dev/null +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve.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 { SPACES } from '../../common/lib/spaces'; +import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils'; +import { TestUser } from '../../common/lib/types'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + resolveTestSuiteFactory, + TEST_CASES as CASES, + ResolveTestDefinition, +} from '../../common/suites/resolve'; + +const { + SPACE_1: { spaceId: SPACE_1_ID }, +} = SPACES; +const { fail404 } = testCaseFailures; + +const createTestCases = (spaceId: string) => { + // for each permitted (non-403) outcome, if failure !== undefined then we expect + // to receive an error; otherwise, we expect to receive a success result + const normalTypes = [ + CASES.EXACT_MATCH, + { ...CASES.ALIAS_MATCH, ...fail404(spaceId !== SPACE_1_ID) }, + { + ...CASES.CONFLICT, + ...(spaceId !== SPACE_1_ID && { expectedOutcome: 'exactMatch' as 'exactMatch' }), + }, + { ...CASES.DISABLED, ...fail404() }, + { ...CASES.DOES_NOT_EXIST, ...fail404() }, + ]; + const hiddenType = [{ ...CASES.HIDDEN, ...fail404() }]; + const allTypes = normalTypes.concat(hiddenType); + return { normalTypes, hiddenType, allTypes }; +}; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + + const { addTests, createTestDefinitions } = resolveTestSuiteFactory(esArchiver, supertest); + const createTests = (spaceId: string) => { + const { normalTypes, hiddenType, allTypes } = createTestCases(spaceId); + // use singleRequest to reduce execution time and/or test combined cases + return { + unauthorized: createTestDefinitions(allTypes, true), + authorized: [ + createTestDefinitions(normalTypes, false), + createTestDefinitions(hiddenType, true), + ].flat(), + superuser: createTestDefinitions(allTypes, false), + }; + }; + + describe('_resolve', () => { + getTestScenarios().securityAndSpaces.forEach(({ spaceId, users }) => { + const suffix = ` within the ${spaceId} space`; + const { unauthorized, authorized, superuser } = createTests(spaceId); + const _addTests = (user: TestUser, tests: ResolveTestDefinition[]) => { + addTests(`${user.description}${suffix}`, { user, spaceId, tests }); + }; + + [users.noAccess, users.legacyAll, users.allAtOtherSpace].forEach((user) => { + _addTests(user, unauthorized); + }); + [ + users.dualAll, + users.dualRead, + users.allGlobally, + users.readGlobally, + users.allAtSpace, + users.readAtSpace, + ].forEach((user) => { + _addTests(user, authorized); + }); + _addTests(users.superuser, superuser); + }); + }); +} diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/index.ts b/x-pack/test/saved_object_api_integration/security_only/apis/index.ts index c52ba3f595711..46b0992480764 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/index.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/index.ts @@ -28,6 +28,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./import')); loadTestFile(require.resolve('./resolve_import_errors')); + loadTestFile(require.resolve('./resolve')); loadTestFile(require.resolve('./update')); }); } diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/resolve.ts b/x-pack/test/saved_object_api_integration/security_only/apis/resolve.ts new file mode 100644 index 0000000000000..9f37f97881071 --- /dev/null +++ b/x-pack/test/saved_object_api_integration/security_only/apis/resolve.ts @@ -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 { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils'; +import { TestUser } from '../../common/lib/types'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + resolveTestSuiteFactory, + TEST_CASES as CASES, + ResolveTestDefinition, +} from '../../common/suites/resolve'; + +const { fail404 } = testCaseFailures; + +const createTestCases = () => { + // for each permitted (non-403) outcome, if failure !== undefined then we expect + // to receive an error; otherwise, we expect to receive a success result + const normalTypes = [ + { ...CASES.EXACT_MATCH }, + { ...CASES.ALIAS_MATCH, ...fail404() }, + { ...CASES.CONFLICT, expectedOutcome: 'exactMatch' as 'exactMatch' }, + { ...CASES.DISABLED, ...fail404() }, + { ...CASES.DOES_NOT_EXIST, ...fail404() }, + ]; + const hiddenType = [{ ...CASES.HIDDEN, ...fail404() }]; + const allTypes = normalTypes.concat(hiddenType); + return { normalTypes, hiddenType, allTypes }; +}; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + + const { addTests, createTestDefinitions } = resolveTestSuiteFactory(esArchiver, supertest); + const createTests = () => { + const { normalTypes, hiddenType, allTypes } = createTestCases(); + return { + unauthorized: createTestDefinitions(allTypes, true), + authorized: [ + createTestDefinitions(normalTypes, false), + createTestDefinitions(hiddenType, true), + ].flat(), + superuser: createTestDefinitions(allTypes, false), + }; + }; + + describe('_resolve', () => { + getTestScenarios().security.forEach(({ users }) => { + const { unauthorized, authorized, superuser } = createTests(); + const _addTests = (user: TestUser, tests: ResolveTestDefinition[]) => { + addTests(user.description, { user, tests }); + }; + + [ + users.noAccess, + users.legacyAll, + users.allAtDefaultSpace, + users.readAtDefaultSpace, + users.allAtSpace1, + users.readAtSpace1, + ].forEach((user) => { + _addTests(user, unauthorized); + }); + [users.dualAll, users.dualRead, users.allGlobally, users.readGlobally].forEach((user) => { + _addTests(user, authorized); + }); + _addTests(users.superuser, superuser); + }); + }); +} diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/index.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/index.ts index c8050733fc6e9..137596bc20c4c 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/index.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/index.ts @@ -20,6 +20,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./import')); loadTestFile(require.resolve('./resolve_import_errors')); + loadTestFile(require.resolve('./resolve')); loadTestFile(require.resolve('./update')); }); } diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve.ts new file mode 100644 index 0000000000000..a6f76fc80044d --- /dev/null +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve.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 { SPACES } from '../../common/lib/spaces'; +import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { resolveTestSuiteFactory, TEST_CASES as CASES } from '../../common/suites/resolve'; + +const { + SPACE_1: { spaceId: SPACE_1_ID }, +} = SPACES; +const { fail404 } = testCaseFailures; + +const createTestCases = (spaceId: string) => [ + // for each outcome, if failure !== undefined then we expect to receive + // an error; otherwise, we expect to receive a success result + CASES.EXACT_MATCH, + { ...CASES.ALIAS_MATCH, ...fail404(spaceId !== SPACE_1_ID) }, + { + ...CASES.CONFLICT, + ...(spaceId !== SPACE_1_ID && { expectedOutcome: 'exactMatch' as 'exactMatch' }), + }, + { ...CASES.DISABLED, ...fail404() }, + { ...CASES.HIDDEN, ...fail404() }, + { ...CASES.DOES_NOT_EXIST, ...fail404() }, +]; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + const { addTests, createTestDefinitions } = resolveTestSuiteFactory(esArchiver, supertest); + const createTests = (spaceId: string) => { + const testCases = createTestCases(spaceId); + return createTestDefinitions(testCases, false, { spaceId }); + }; + + describe('_resolve', () => { + getTestScenarios().spaces.forEach(({ spaceId }) => { + const tests = createTests(spaceId); + addTests(`within the ${spaceId} space`, { spaceId, tests }); + }); + }); +} From 3b728b73cf5720b48e9759c7bb8b845e18ba5b57 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 20 Jan 2021 19:29:04 -0500 Subject: [PATCH 32/83] [Fleet] Use fleet server indices for enrollment keys and to list agents with a feature flag (#86179) --- .../plugins/fleet/common/constants/agent.ts | 2 + .../fleet/common/constants/agent_policy.ts | 2 +- .../common/constants/enrollment_api_key.ts | 2 + .../fleet/common/services/agent_status.ts | 4 +- x-pack/plugins/fleet/common/types/index.ts | 1 + .../fleet/common/types/models/agent_policy.ts | 34 +++ .../common/types/models/enrollment_api_key.ts | 28 +++ .../fleet/mock/plugin_configuration.ts | 1 + .../server/collectors/agent_collectors.ts | 12 +- .../fleet/server/collectors/helpers.ts | 9 +- .../fleet/server/collectors/register.ts | 6 +- .../plugins/fleet/server/constants/index.ts | 3 + x-pack/plugins/fleet/server/index.ts | 1 + x-pack/plugins/fleet/server/mocks.ts | 7 +- x-pack/plugins/fleet/server/plugin.ts | 11 + .../server/routes/agent/acks_handlers.test.ts | 10 +- .../server/routes/agent/acks_handlers.ts | 8 +- .../routes/agent/actions_handlers.test.ts | 14 +- .../server/routes/agent/actions_handlers.ts | 3 +- .../fleet/server/routes/agent/handlers.ts | 34 ++- .../fleet/server/routes/agent/index.ts | 6 + .../server/routes/agent/unenroll_handler.ts | 8 +- .../server/routes/agent/upgrade_handler.ts | 5 +- .../server/routes/agent_policy/handlers.ts | 14 +- .../routes/enrollment_api_key/handler.ts | 29 ++- .../routes/package_policy/handlers.test.ts | 4 +- .../server/routes/package_policy/handlers.ts | 17 +- .../fleet/server/routes/settings/index.ts | 4 +- .../fleet/server/routes/setup/handlers.ts | 8 +- .../server/services/agent_policy.test.ts | 10 +- .../fleet/server/services/agent_policy.ts | 117 ++++++++-- .../server/services/agent_policy_update.ts | 7 +- .../fleet/server/services/agents/acks.test.ts | 16 +- .../fleet/server/services/agents/acks.ts | 7 +- .../fleet/server/services/agents/actions.ts | 8 +- .../server/services/agents/checkin/index.ts | 9 +- .../server/services/agents/checkin/state.ts | 5 +- .../agents/checkin/state_new_actions.ts | 5 +- .../fleet/server/services/agents/crud.ts | 165 ++++---------- .../services/agents/crud_fleet_server.ts | 197 +++++++++++++++++ .../fleet/server/services/agents/crud_so.ts | 195 +++++++++++++++++ .../fleet/server/services/agents/helpers.ts | 21 ++ .../fleet/server/services/agents/reassign.ts | 6 +- .../server/services/agents/status.test.ts | 14 +- .../fleet/server/services/agents/status.ts | 8 +- .../fleet/server/services/agents/unenroll.ts | 16 +- .../fleet/server/services/agents/update.ts | 5 +- .../fleet/server/services/agents/upgrade.ts | 5 +- .../services/api_keys/enrollment_api_key.ts | 166 +++----------- .../enrollment_api_key_fleet_server.ts | 205 ++++++++++++++++++ .../api_keys/enrollment_api_key_so.ts | 174 +++++++++++++++ .../fleet/server/services/app_context.ts | 20 +- .../server/services/fleet_server_migration.ts | 75 +++++++ x-pack/plugins/fleet/server/services/index.ts | 8 +- .../server/services/package_policy.test.ts | 4 +- .../fleet/server/services/package_policy.ts | 29 ++- .../fleet/server/services/setup.test.ts | 6 +- x-pack/plugins/fleet/server/services/setup.ts | 15 +- x-pack/plugins/fleet/server/types/index.tsx | 2 + .../endpoint/lib/policy/license_watch.test.ts | 15 +- .../endpoint/lib/policy/license_watch.ts | 14 +- .../endpoint/routes/metadata/handlers.ts | 7 +- .../metadata/support/agent_status.test.ts | 37 +++- .../routes/metadata/support/agent_status.ts | 5 +- .../routes/metadata/support/unenroll.test.ts | 15 +- .../routes/metadata/support/unenroll.ts | 9 +- .../server/endpoint/routes/policy/handlers.ts | 1 + .../server/endpoint/routes/policy/service.ts | 13 +- .../manifest_manager/manifest_manager.test.ts | 2 +- .../manifest_manager/manifest_manager.ts | 8 +- .../security_solution/server/plugin.ts | 1 + 71 files changed, 1528 insertions(+), 406 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/agents/crud_fleet_server.ts create mode 100644 x-pack/plugins/fleet/server/services/agents/crud_so.ts create mode 100644 x-pack/plugins/fleet/server/services/agents/helpers.ts create mode 100644 x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_fleet_server.ts create mode 100644 x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_so.ts create mode 100644 x-pack/plugins/fleet/server/services/fleet_server_migration.ts diff --git a/x-pack/plugins/fleet/common/constants/agent.ts b/x-pack/plugins/fleet/common/constants/agent.ts index 30b8a6b740609..8bfb32b5ed2b0 100644 --- a/x-pack/plugins/fleet/common/constants/agent.ts +++ b/x-pack/plugins/fleet/common/constants/agent.ts @@ -22,3 +22,5 @@ export const AGENT_UPDATE_ACTIONS_INTERVAL_MS = 5000; export const AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS = 1000; export const AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL = 5; + +export const AGENTS_INDEX = '.fleet-agents'; diff --git a/x-pack/plugins/fleet/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts index 5445fbcacf2ec..2dd21fb41b663 100644 --- a/x-pack/plugins/fleet/common/constants/agent_policy.ts +++ b/x-pack/plugins/fleet/common/constants/agent_policy.ts @@ -6,7 +6,7 @@ import { defaultPackages } from './epm'; import { AgentPolicy } from '../types'; export const AGENT_POLICY_SAVED_OBJECT_TYPE = 'ingest-agent-policies'; - +export const AGENT_POLICY_INDEX = '.fleet-policies'; export const agentPolicyStatuses = { Active: 'active', Inactive: 'inactive', diff --git a/x-pack/plugins/fleet/common/constants/enrollment_api_key.ts b/x-pack/plugins/fleet/common/constants/enrollment_api_key.ts index fd28b6632b15c..ce774f2212461 100644 --- a/x-pack/plugins/fleet/common/constants/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/common/constants/enrollment_api_key.ts @@ -5,3 +5,5 @@ */ export const ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE = 'fleet-enrollment-api-keys'; + +export const ENROLLMENT_API_KEYS_INDEX = '.fleet-enrollment-api-keys'; diff --git a/x-pack/plugins/fleet/common/services/agent_status.ts b/x-pack/plugins/fleet/common/services/agent_status.ts index 4cf35398bab24..c99cfd11b763f 100644 --- a/x-pack/plugins/fleet/common/services/agent_status.ts +++ b/x-pack/plugins/fleet/common/services/agent_status.ts @@ -41,7 +41,7 @@ export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentSta } export function buildKueryForEnrollingAgents() { - return `not ${AGENT_SAVED_OBJECT_TYPE}.last_checkin:*`; + return `not (${AGENT_SAVED_OBJECT_TYPE}.last_checkin:*)`; } export function buildKueryForUnenrollingAgents() { @@ -53,7 +53,7 @@ export function buildKueryForOnlineAgents() { } export function buildKueryForErrorAgents() { - return `( ${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:error or ${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:degraded )`; + return `${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:error or ${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:degraded`; } export function buildKueryForOfflineAgents() { diff --git a/x-pack/plugins/fleet/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts index e0827ef7cf40f..b023052b1328d 100644 --- a/x-pack/plugins/fleet/common/types/index.ts +++ b/x-pack/plugins/fleet/common/types/index.ts @@ -11,6 +11,7 @@ export interface FleetConfigType { registryUrl?: string; registryProxyUrl?: string; agents: { + fleetServerEnabled: boolean; enabled: boolean; tlsCheckDisabled: boolean; pollingRequestTimeout: number; diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index 75bb2998f2d92..2e29fe148b35f 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -80,3 +80,37 @@ export interface FullAgentPolicyKibanaConfig { protocol: string; path?: string; } + +// Generated from Fleet Server schema.json + +/** + * A policy that an Elastic Agent is attached to + */ +export interface FleetServerPolicy { + /** + * Date/time the policy revision was created + */ + '@timestamp'?: string; + /** + * The ID of the policy + */ + policy_id: string; + /** + * The revision index of the policy + */ + revision_idx: number; + /** + * The coordinator index of the policy + */ + coordinator_idx: number; + /** + * The opaque payload. + */ + data: { + [k: string]: unknown; + }; + /** + * True when this policy is the default policy to start Fleet Server + */ + default_fleet_server: boolean; +} diff --git a/x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts b/x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts index f39076ce1027b..81dc6889f9946 100644 --- a/x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts @@ -15,3 +15,31 @@ export interface EnrollmentAPIKey { } export type EnrollmentAPIKeySOAttributes = Omit; + +// Generated + +/** + * An Elastic Agent enrollment API key + */ +export interface FleetServerEnrollmentAPIKey { + /** + * True when the key is active + */ + active?: boolean; + /** + * The unique identifier for the enrollment key, currently xid + */ + api_key_id: string; + /** + * Api key + */ + api_key: string; + /** + * Enrollment key name + */ + name?: string; + policy_id?: string; + expire_at?: string; + created_at?: string; + updated_at?: string; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts b/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts index 735c1d11a9837..62896289af514 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts @@ -13,6 +13,7 @@ export const createConfigurationMock = (): FleetConfigType => { registryProxyUrl: '', agents: { enabled: true, + fleetServerEnabled: false, tlsCheckDisabled: true, pollingRequestTimeout: 1000, maxConcurrentConnections: 100, diff --git a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts index fe5e5fa735b24..8925f3386dfb8 100644 --- a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts +++ b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClient } from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClient } from 'kibana/server'; import * as AgentService from '../services/agents'; export interface AgentUsage { total: number; @@ -13,9 +13,12 @@ export interface AgentUsage { offline: number; } -export const getAgentUsage = async (soClient?: SavedObjectsClient): Promise => { +export const getAgentUsage = async ( + soClient?: SavedObjectsClient, + esClient?: ElasticsearchClient +): Promise => { // TODO: unsure if this case is possible at all. - if (!soClient) { + if (!soClient || !esClient) { return { total: 0, online: 0, @@ -24,7 +27,8 @@ export const getAgentUsage = async (soClient?: SavedObjectsClient): Promise { return core.getStartServices().then(async ([coreStart]) => { const savedObjectsRepo = coreStart.savedObjects.createInternalRepository(); - return new SavedObjectsClient(savedObjectsRepo); + const esClient = coreStart.elasticsearch.client.asInternalUser; + return [new SavedObjectsClient(savedObjectsRepo), esClient]; }); } diff --git a/x-pack/plugins/fleet/server/collectors/register.ts b/x-pack/plugins/fleet/server/collectors/register.ts index 35517e6a7a700..7ec04ca6fee41 100644 --- a/x-pack/plugins/fleet/server/collectors/register.ts +++ b/x-pack/plugins/fleet/server/collectors/register.ts @@ -8,7 +8,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { CoreSetup } from 'kibana/server'; import { getIsAgentsEnabled } from './config_collectors'; import { AgentUsage, getAgentUsage } from './agent_collectors'; -import { getInternalSavedObjectsClient } from './helpers'; +import { getInternalClients } from './helpers'; import { PackageUsage, getPackageUsage } from './package_collectors'; import { FleetConfigType } from '..'; @@ -34,10 +34,10 @@ export function registerFleetUsageCollector( type: 'fleet', isReady: () => true, fetch: async () => { - const soClient = await getInternalSavedObjectsClient(core); + const [soClient, esClient] = await getInternalClients(core); return { agents_enabled: getIsAgentsEnabled(config), - agents: await getAgentUsage(soClient), + agents: await getAgentUsage(soClient, esClient), packages: await getPackageUsage(soClient), }; }, diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index dbf2fbc362a45..37f8ab041e5a3 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -47,4 +47,7 @@ export { // Defaults DEFAULT_AGENT_POLICY, DEFAULT_OUTPUT, + // Fleet Server index + ENROLLMENT_API_KEYS_INDEX, + AGENTS_INDEX, } from '../../common'; diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 1fe7013944fd7..672911ccf6fe0 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -37,6 +37,7 @@ export const config: PluginConfigDescriptor = { registryProxyUrl: schema.maybe(schema.uri({ scheme: ['http', 'https'] })), agents: schema.object({ enabled: schema.boolean({ defaultValue: true }), + fleetServerEnabled: schema.boolean({ defaultValue: false }), tlsCheckDisabled: schema.boolean({ defaultValue: false }), pollingRequestTimeout: schema.number({ defaultValue: AGENT_POLLING_REQUEST_TIMEOUT_MS, diff --git a/x-pack/plugins/fleet/server/mocks.ts b/x-pack/plugins/fleet/server/mocks.ts index 4a897d80acd6d..bf659294f514c 100644 --- a/x-pack/plugins/fleet/server/mocks.ts +++ b/x-pack/plugins/fleet/server/mocks.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks'; +import { + elasticsearchServiceMock, + loggingSystemMock, + savedObjectsServiceMock, +} from 'src/core/server/mocks'; import { FleetAppContext } from './plugin'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { securityMock } from '../../security/server/mocks'; @@ -13,6 +17,7 @@ import { AgentPolicyServiceInterface, AgentService } from './services'; export const createAppContextStartContractMock = (): FleetAppContext => { return { + elasticsearch: elasticsearchServiceMock.createStart(), encryptedSavedObjectsStart: encryptedSavedObjectsMock.createStart(), savedObjects: savedObjectsServiceMock.createStartContract(), security: securityMock.createStart(), diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 8ce17a00acf33..253b614dc228a 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -8,6 +8,7 @@ import { first } from 'rxjs/operators'; import { CoreSetup, CoreStart, + ElasticsearchServiceStart, Logger, Plugin, PluginInitializerContext, @@ -80,6 +81,7 @@ import { agentCheckinState } from './services/agents/checkin/state'; import { registerFleetUsageCollector } from './collectors/register'; import { getInstallation } from './services/epm/packages'; import { makeRouterEnforcingSuperuser } from './routes/security'; +import { runFleetServerMigration } from './services/fleet_server_migration'; export interface FleetSetupDeps { licensing: LicensingPluginSetup; @@ -96,6 +98,7 @@ export interface FleetStartDeps { } export interface FleetAppContext { + elasticsearch: ElasticsearchServiceStart; encryptedSavedObjectsStart?: EncryptedSavedObjectsPluginStart; encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup; security?: SecurityPluginStart; @@ -276,6 +279,7 @@ export class FleetPlugin public async start(core: CoreStart, plugins: FleetStartDeps): Promise { await appContextService.start({ + elasticsearch: core.elasticsearch, encryptedSavedObjectsStart: plugins.encryptedSavedObjects, encryptedSavedObjectsSetup: this.encryptedSavedObjectsSetup, security: plugins.security, @@ -291,6 +295,13 @@ export class FleetPlugin licenseService.start(this.licensing$); agentCheckinState.start(); + const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled; + if (fleetServerEnabled) { + // We need licence to be initialized before using the SO service. + await this.licensing$.pipe(first()).toPromise(); + await runFleetServerMigration(); + } + return { esIndexPatternService: new ESIndexPatternSavedObjectService(), packageService: { diff --git a/x-pack/plugins/fleet/server/routes/agent/acks_handlers.test.ts b/x-pack/plugins/fleet/server/routes/agent/acks_handlers.test.ts index 3d7f5c4a17adb..d775979527afb 100644 --- a/x-pack/plugins/fleet/server/routes/agent/acks_handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/agent/acks_handlers.test.ts @@ -6,11 +6,16 @@ import { postAgentAcksHandlerBuilder } from './acks_handlers'; import { + ElasticsearchClient, KibanaResponseFactory, RequestHandlerContext, SavedObjectsClientContract, } from 'kibana/server'; -import { httpServerMock, savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { + elasticsearchServiceMock, + httpServerMock, + savedObjectsClientMock, +} from '../../../../../../src/core/server/mocks'; import { PostAgentAcksResponse } from '../../../common/types/rest_spec'; import { AckEventSchema } from '../../types/models'; import { AcksService } from '../../services/agents'; @@ -45,9 +50,11 @@ describe('test acks schema', () => { describe('test acks handlers', () => { let mockResponse: jest.Mocked; let mockSavedObjectsClient: jest.Mocked; + let mockElasticsearchClient: jest.Mocked; beforeEach(() => { mockSavedObjectsClient = savedObjectsClientMock.create(); + mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockResponse = httpServerMock.createResponseFactory(); }); @@ -81,6 +88,7 @@ describe('test acks handlers', () => { id: 'agent', }), getSavedObjectsClientContract: jest.fn().mockReturnValueOnce(mockSavedObjectsClient), + getElasticsearchClientContract: jest.fn().mockReturnValueOnce(mockElasticsearchClient), saveAgentEvents: jest.fn(), } as jest.Mocked; diff --git a/x-pack/plugins/fleet/server/routes/agent/acks_handlers.ts b/x-pack/plugins/fleet/server/routes/agent/acks_handlers.ts index fb320b01dea97..28cd7e57d6537 100644 --- a/x-pack/plugins/fleet/server/routes/agent/acks_handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/acks_handlers.ts @@ -18,6 +18,7 @@ export const postAgentAcksHandlerBuilder = function ( return async (context, request, response) => { try { const soClient = ackService.getSavedObjectsClientContract(request); + const esClient = ackService.getElasticsearchClientContract(); const agent = await ackService.authenticateAgentWithAccessToken(soClient, request); const agentEvents = request.body.events as AgentEvent[]; @@ -33,7 +34,12 @@ export const postAgentAcksHandlerBuilder = function ( }); } - const agentActions = await ackService.acknowledgeAgentActions(soClient, agent, agentEvents); + const agentActions = await ackService.acknowledgeAgentActions( + soClient, + esClient, + agent, + agentEvents + ); if (agentActions.length > 0) { await ackService.saveAgentEvents(soClient, agentEvents); diff --git a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts index 2f08846642985..2674e8c5cedd0 100644 --- a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts @@ -6,11 +6,16 @@ import { NewAgentActionSchema } from '../../types/models'; import { + ElasticsearchClient, KibanaResponseFactory, RequestHandlerContext, SavedObjectsClientContract, } from 'kibana/server'; -import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks'; +import { + elasticsearchServiceMock, + savedObjectsClientMock, + httpServerMock, +} from 'src/core/server/mocks'; import { ActionsService } from '../../services/agents'; import { AgentAction } from '../../../common/types/models'; import { postNewAgentActionHandlerBuilder } from './actions_handlers'; @@ -41,9 +46,11 @@ describe('test actions handlers schema', () => { describe('test actions handlers', () => { let mockResponse: jest.Mocked; let mockSavedObjectsClient: jest.Mocked; + let mockElasticsearchClient: jest.Mocked; beforeEach(() => { mockSavedObjectsClient = savedObjectsClientMock.create(); + mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockResponse = httpServerMock.createResponseFactory(); }); @@ -84,6 +91,11 @@ describe('test actions handlers', () => { savedObjects: { client: mockSavedObjectsClient, }, + elasticsearch: { + client: { + asInternalUser: mockElasticsearchClient, + }, + }, }, } as unknown) as RequestHandlerContext, mockRequest, diff --git a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts index 64a7795cc9dac..04b92296439c5 100644 --- a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts @@ -23,8 +23,9 @@ export const postNewAgentActionHandlerBuilder = function ( return async (context, request, response) => { try { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asInternalUser; - const agent = await actionsService.getAgent(soClient, request.params.agentId); + const agent = await actionsService.getAgent(soClient, esClient, request.params.agentId); const newAgentAction = request.body.action; diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index a867196f9762f..0cd53a2313d2a 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -39,8 +39,10 @@ export const getAgentHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; + try { - const agent = await AgentService.getAgent(soClient, request.params.agentId); + const agent = await AgentService.getAgent(soClient, esClient, request.params.agentId); const body: GetOneAgentResponse = { item: { @@ -98,8 +100,10 @@ export const deleteAgentHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; + try { - await AgentService.deleteAgent(soClient, request.params.agentId); + await AgentService.deleteAgent(soClient, esClient, request.params.agentId); const body = { action: 'deleted', @@ -124,11 +128,13 @@ export const updateAgentHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; + try { await AgentService.updateAgent(soClient, request.params.agentId, { userProvidedMetatada: request.body.user_provided_metadata, }); - const agent = await AgentService.getAgent(soClient, request.params.agentId); + const agent = await AgentService.getAgent(soClient, esClient, request.params.agentId); const body = { item: { @@ -156,6 +162,7 @@ export const postAgentCheckinHandler: RequestHandler< > = async (context, request, response) => { try { const soClient = appContextService.getInternalUserSOClient(request); + const esClient = appContextService.getInternalUserESClient(); const agent = await AgentService.authenticateAgentWithAccessToken(soClient, request); const abortController = new AbortController(); request.events.aborted$.subscribe(() => { @@ -164,6 +171,7 @@ export const postAgentCheckinHandler: RequestHandler< const signal = abortController.signal; const { actions } = await AgentService.agentCheckin( soClient, + esClient, agent, { events: request.body.events || [], @@ -234,8 +242,10 @@ export const getAgentsHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; + try { - const { agents, total, page, perPage } = await AgentService.listAgents(soClient, { + const { agents, total, page, perPage } = await AgentService.listAgents(soClient, esClient, { page: request.query.page, perPage: request.query.perPage, showInactive: request.query.showInactive, @@ -243,7 +253,7 @@ export const getAgentsHandler: RequestHandler< kuery: request.query.kuery, }); const totalInactive = request.query.showInactive - ? await AgentService.countInactiveAgents(soClient, { + ? await AgentService.countInactiveAgents(soClient, esClient, { kuery: request.query.kuery, }) : 0; @@ -270,8 +280,14 @@ export const putAgentsReassignHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asInternalUser; try { - await AgentService.reassignAgent(soClient, request.params.agentId, request.body.policy_id); + await AgentService.reassignAgent( + soClient, + esClient, + request.params.agentId, + request.body.policy_id + ); const body: PutAgentReassignResponse = {}; return response.ok({ body }); @@ -293,16 +309,19 @@ export const postBulkAgentsReassignHandler: RequestHandler< } const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asInternalUser; try { // Reassign by array of IDs const result = Array.isArray(request.body.agents) ? await AgentService.reassignAgents( soClient, + esClient, { agentIds: request.body.agents }, request.body.policy_id ) : await AgentService.reassignAgents( soClient, + esClient, { kuery: request.body.agents }, request.body.policy_id ); @@ -326,10 +345,13 @@ export const getAgentStatusForAgentPolicyHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; + try { // TODO change path const results = await AgentService.getAgentStatusForAgentPolicy( soClient, + esClient, request.query.policyId, request.query.kuery ); diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index 54a30fbc9320f..c088349a995af 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -294,6 +294,9 @@ export const registerElasticAgentRoutes = (router: IRouter, config: FleetConfigT getSavedObjectsClientContract: appContextService.getInternalUserSOClient.bind( appContextService ), + getElasticsearchClientContract: appContextService.getInternalUserESClient.bind( + appContextService + ), saveAgentEvents: AgentService.saveAgentEvents, }) ); @@ -313,6 +316,9 @@ export const registerElasticAgentRoutes = (router: IRouter, config: FleetConfigT getSavedObjectsClientContract: appContextService.getInternalUserSOClient.bind( appContextService ), + getElasticsearchClientContract: appContextService.getInternalUserESClient.bind( + appContextService + ), saveAgentEvents: AgentService.saveAgentEvents, }) ); diff --git a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts index 861d7c45c6f0a..41c183789f9fd 100644 --- a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts @@ -18,9 +18,10 @@ export const postAgentUnenrollHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asInternalUser; try { if (request.body?.force === true) { - await AgentService.forceUnenrollAgent(soClient, request.params.agentId); + await AgentService.forceUnenrollAgent(soClient, esClient, request.params.agentId); } else { await AgentService.unenrollAgent(soClient, request.params.agentId); } @@ -44,14 +45,15 @@ export const postBulkAgentsUnenrollHandler: RequestHandler< }); } const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asInternalUser; const unenrollAgents = request.body?.force === true ? AgentService.forceUnenrollAgents : AgentService.unenrollAgents; try { if (Array.isArray(request.body.agents)) { - await unenrollAgents(soClient, { agentIds: request.body.agents }); + await unenrollAgents(soClient, esClient, { agentIds: request.body.agents }); } else { - await unenrollAgents(soClient, { kuery: request.body.agents }); + await unenrollAgents(soClient, esClient, { kuery: request.body.agents }); } const body: PostBulkAgentUnenrollResponse = {}; diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts index 93e6609167a2e..7b068674d3829 100644 --- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts @@ -83,6 +83,7 @@ export const postBulkAgentsUpgradeHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asInternalUser; const { version, source_uri: sourceUri, agents, force } = request.body; const kibanaVersion = appContextService.getKibanaVersion(); try { @@ -98,14 +99,14 @@ export const postBulkAgentsUpgradeHandler: RequestHandler< try { if (Array.isArray(agents)) { - await AgentService.sendUpgradeAgentsActions(soClient, { + await AgentService.sendUpgradeAgentsActions(soClient, esClient, { agentIds: agents, sourceUri, version, force, }); } else { - await AgentService.sendUpgradeAgentsActions(soClient, { + await AgentService.sendUpgradeAgentsActions(soClient, esClient, { kuery: agents, sourceUri, version, diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index 25aaf5f9a4656..8f7fd4427f586 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -39,6 +39,7 @@ export const getAgentPoliciesHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; const { full: withPackagePolicies = false, ...restOfQuery } = request.query; try { const { items, total, page, perPage } = await agentPolicyService.list(soClient, { @@ -55,7 +56,7 @@ export const getAgentPoliciesHandler: RequestHandler< await bluebird.map( items, (agentPolicy: GetAgentPoliciesResponseItem) => - listAgents(soClient, { + listAgents(soClient, esClient, { showInactive: false, perPage: 0, page: 1, @@ -100,6 +101,7 @@ export const createAgentPolicyHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; const withSysMonitoring = request.query.sys_monitoring ?? false; @@ -109,7 +111,7 @@ export const createAgentPolicyHandler: RequestHandler< AgentPolicy, NewPackagePolicy | undefined >([ - agentPolicyService.create(soClient, request.body, { + agentPolicyService.create(soClient, esClient, request.body, { user, }), // If needed, retrieve System package information and build a new package policy for the system package @@ -126,7 +128,7 @@ export const createAgentPolicyHandler: RequestHandler< if (withSysMonitoring && newSysPackagePolicy !== undefined && agentPolicy !== undefined) { newSysPackagePolicy.policy_id = agentPolicy.id; newSysPackagePolicy.namespace = agentPolicy.namespace; - await packagePolicyService.create(soClient, callCluster, newSysPackagePolicy, { + await packagePolicyService.create(soClient, esClient, callCluster, newSysPackagePolicy, { user, bumpRevision: false, }); @@ -152,10 +154,12 @@ export const updateAgentPolicyHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; const user = await appContextService.getSecurity()?.authc.getCurrentUser(request); try { const agentPolicy = await agentPolicyService.update( soClient, + esClient, request.params.agentPolicyId, request.body, { @@ -177,10 +181,12 @@ export const copyAgentPolicyHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; const user = await appContextService.getSecurity()?.authc.getCurrentUser(request); try { const agentPolicy = await agentPolicyService.copy( soClient, + esClient, request.params.agentPolicyId, request.body, { @@ -203,9 +209,11 @@ export const deleteAgentPoliciesHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; try { const body: DeleteAgentPolicyResponse = await agentPolicyService.delete( soClient, + esClient, request.body.agentPolicyId ); return response.ok({ diff --git a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts index afecd7bd7d828..4f54b4e155ea3 100644 --- a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts +++ b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts @@ -26,12 +26,18 @@ export const getEnrollmentApiKeysHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; + try { - const { items, total, page, perPage } = await APIKeyService.listEnrollmentApiKeys(soClient, { - page: request.query.page, - perPage: request.query.perPage, - kuery: request.query.kuery, - }); + const { items, total, page, perPage } = await APIKeyService.listEnrollmentApiKeys( + soClient, + esClient, + { + page: request.query.page, + perPage: request.query.perPage, + kuery: request.query.kuery, + } + ); const body: GetEnrollmentAPIKeysResponse = { list: items, total, page, perPage }; return response.ok({ body }); @@ -45,8 +51,9 @@ export const postEnrollmentApiKeyHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; try { - const apiKey = await APIKeyService.generateEnrollmentAPIKey(soClient, { + const apiKey = await APIKeyService.generateEnrollmentAPIKey(soClient, esClient, { name: request.body.name, expiration: request.body.expiration, agentPolicyId: request.body.policy_id, @@ -64,8 +71,9 @@ export const deleteEnrollmentApiKeyHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; try { - await APIKeyService.deleteEnrollmentApiKey(soClient, request.params.keyId); + await APIKeyService.deleteEnrollmentApiKey(soClient, esClient, request.params.keyId); const body: DeleteEnrollmentAPIKeyResponse = { action: 'deleted' }; @@ -84,8 +92,13 @@ export const getOneEnrollmentApiKeyHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; try { - const apiKey = await APIKeyService.getEnrollmentAPIKey(soClient, request.params.keyId); + const apiKey = await APIKeyService.getEnrollmentAPIKey( + soClient, + esClient, + request.params.keyId + ); const body: GetOneEnrollmentAPIKeyResponse = { item: apiKey }; return response.ok({ body }); diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts index f9fd6047baa77..90a06563efce5 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts @@ -25,7 +25,7 @@ jest.mock('../../services/package_policy', (): { compilePackagePolicyInputs: jest.fn((packageInfo, dataInputs) => Promise.resolve(dataInputs)), buildPackagePolicyFromPackage: jest.fn(), bulkCreate: jest.fn(), - create: jest.fn((soClient, callCluster, newData) => + create: jest.fn((soClient, esClient, callCluster, newData) => Promise.resolve({ ...newData, inputs: newData.inputs.map((input) => ({ @@ -201,7 +201,7 @@ describe('When calling package policy', () => { ); await routeHandler(context, request, response); expect(response.ok).toHaveBeenCalled(); - expect(packagePolicyServiceMock.create.mock.calls[0][2]).toEqual({ + expect(packagePolicyServiceMock.create.mock.calls[0][3]).toEqual({ policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c', description: '', enabled: true, diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index be14970de3e0f..bef33c1c98b62 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -74,6 +74,7 @@ export const createPackagePolicyHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; let newData = { ...request.body }; @@ -86,9 +87,15 @@ export const createPackagePolicyHandler: RequestHandler< ); // Create package policy - const packagePolicy = await packagePolicyService.create(soClient, callCluster, newData, { - user, - }); + const packagePolicy = await packagePolicyService.create( + soClient, + esClient, + callCluster, + newData, + { + user, + } + ); const body: CreatePackagePolicyResponse = { item: packagePolicy }; return response.ok({ body, @@ -110,6 +117,7 @@ export const updatePackagePolicyHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; const packagePolicy = await packagePolicyService.get(soClient, request.params.packagePolicyId); @@ -131,6 +139,7 @@ export const updatePackagePolicyHandler: RequestHandler< const updatedPackagePolicy = await packagePolicyService.update( soClient, + esClient, request.params.packagePolicyId, { ...newData, package: pkg, inputs }, { user } @@ -149,10 +158,12 @@ export const deletePackagePolicyHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; try { const body: DeletePackagePoliciesResponse = await packagePolicyService.delete( soClient, + esClient, request.body.packagePolicyIds, { user } ); diff --git a/x-pack/plugins/fleet/server/routes/settings/index.ts b/x-pack/plugins/fleet/server/routes/settings/index.ts index 4eeff629dc227..6f63043c12a27 100644 --- a/x-pack/plugins/fleet/server/routes/settings/index.ts +++ b/x-pack/plugins/fleet/server/routes/settings/index.ts @@ -36,10 +36,12 @@ export const putSettingsHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; const user = await appContextService.getSecurity()?.authc.getCurrentUser(request); + try { const settings = await settingsService.saveSettings(soClient, request.body); - await agentPolicyService.bumpAllAgentPolicies(soClient, { + await agentPolicyService.bumpAllAgentPolicies(soClient, esClient, { user: user || undefined, }); const body = { diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts index cafccd1895d11..cf0967045f151 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.ts @@ -59,9 +59,10 @@ export const createFleetSetupHandler: RequestHandler< > = async (context, request, response) => { try { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; - await setupIngestManager(soClient, callCluster); - await setupFleet(soClient, callCluster, { + await setupIngestManager(soClient, esClient, callCluster); + await setupFleet(soClient, esClient, callCluster, { forceRecreate: request.body?.forceRecreate ?? false, }); @@ -75,11 +76,12 @@ export const createFleetSetupHandler: RequestHandler< export const FleetSetupHandler: RequestHandler = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; try { const body: PostIngestSetupResponse = { isInitialized: true }; - await setupIngestManager(soClient, callCluster); + await setupIngestManager(soClient, esClient, callCluster); return response.ok({ body, }); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts index f9a8b63bb83ad..de3647bec0164 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; import { agentPolicyService } from './agent_policy'; import { agentPolicyUpdateEventHandler } from './agent_policy_update'; import { Output } from '../types'; @@ -78,7 +78,9 @@ describe('agent policy', () => { revision: 1, monitoring_enabled: ['metrics'], }); - await agentPolicyService.bumpRevision(soClient, 'agent-policy'); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + + await agentPolicyService.bumpRevision(soClient, esClient, 'agent-policy'); expect(agentPolicyUpdateEventHandler).toHaveBeenCalledTimes(1); }); @@ -90,7 +92,9 @@ describe('agent policy', () => { revision: 1, monitoring_enabled: ['metrics'], }); - await agentPolicyService.bumpAllAgentPolicies(soClient); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + + await agentPolicyService.bumpAllAgentPolicies(soClient, esClient, undefined); expect(agentPolicyUpdateEventHandler).toHaveBeenCalledTimes(1); }); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 0fd41d074effa..81d98823b5268 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -5,7 +5,12 @@ */ import { uniq } from 'lodash'; import { safeLoad } from 'js-yaml'; -import { SavedObjectsClientContract, SavedObjectsBulkUpdateResponse } from 'src/core/server'; +import uuid from 'uuid/v4'; +import { + ElasticsearchClient, + SavedObjectsClientContract, + SavedObjectsBulkUpdateResponse, +} from 'src/core/server'; import { AuthenticatedUser } from '../../../security/server'; import { DEFAULT_AGENT_POLICY, @@ -26,6 +31,8 @@ import { agentPolicyStatuses, storedPackagePoliciesToAgentInputs, dataTypes, + FleetServerPolicy, + AGENT_POLICY_INDEX, } from '../../common'; import { AgentPolicyNameExistsError } from '../errors'; import { createAgentPolicyAction, listAgents } from './agents'; @@ -36,20 +43,23 @@ import { getSettings } from './settings'; import { normalizeKuery, escapeSearchQueryPhrase } from './saved_object'; import { getFullAgentPolicyKibanaConfig } from '../../common/services/full_agent_policy_kibana_config'; import { isAgentsSetup } from './agents/setup'; +import { appContextService } from './app_context'; const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE; class AgentPolicyService { private triggerAgentPolicyUpdatedEvent = async ( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, action: 'created' | 'updated' | 'deleted', agentPolicyId: string ) => { - return agentPolicyUpdateEventHandler(soClient, action, agentPolicyId); + return agentPolicyUpdateEventHandler(soClient, esClient, action, agentPolicyId); }; private async _update( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, id: string, agentPolicy: Partial, user?: AuthenticatedUser, @@ -78,14 +88,15 @@ class AgentPolicyService { }); if (options.bumpRevision) { - await this.triggerAgentPolicyUpdatedEvent(soClient, 'updated', id); + await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'updated', id); } return (await this.get(soClient, id)) as AgentPolicy; } public async ensureDefaultAgentPolicy( - soClient: SavedObjectsClientContract + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient ): Promise<{ created: boolean; defaultAgentPolicy: AgentPolicy; @@ -103,7 +114,7 @@ class AgentPolicyService { return { created: true, - defaultAgentPolicy: await this.create(soClient, newDefaultAgentPolicy), + defaultAgentPolicy: await this.create(soClient, esClient, newDefaultAgentPolicy), }; } @@ -118,6 +129,7 @@ class AgentPolicyService { public async create( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, agentPolicy: NewAgentPolicy, options?: { id?: string; user?: AuthenticatedUser } ): Promise { @@ -134,7 +146,7 @@ class AgentPolicyService { ); if (!agentPolicy.is_default) { - await this.triggerAgentPolicyUpdatedEvent(soClient, 'created', newSo.id); + await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'created', newSo.id); } return { id: newSo.id, ...newSo.attributes }; @@ -244,6 +256,7 @@ class AgentPolicyService { public async update( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, id: string, agentPolicy: Partial, options?: { user?: AuthenticatedUser } @@ -254,11 +267,12 @@ class AgentPolicyService { name: agentPolicy.name, }); } - return this._update(soClient, id, agentPolicy, options?.user); + return this._update(soClient, esClient, id, agentPolicy, options?.user); } public async copy( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, id: string, newAgentPolicyProps: Pick, options?: { user?: AuthenticatedUser } @@ -272,6 +286,7 @@ class AgentPolicyService { const { namespace, monitoring_enabled } = baseAgentPolicy; const newAgentPolicy = await this.create( soClient, + esClient, { namespace, monitoring_enabled, @@ -288,10 +303,16 @@ class AgentPolicyService { return newPackagePolicy; } ); - await packagePolicyService.bulkCreate(soClient, newPackagePolicies, newAgentPolicy.id, { - ...options, - bumpRevision: false, - }); + await packagePolicyService.bulkCreate( + soClient, + esClient, + newPackagePolicies, + newAgentPolicy.id, + { + ...options, + bumpRevision: false, + } + ); } // Get updated agent policy @@ -307,15 +328,18 @@ class AgentPolicyService { public async bumpRevision( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, id: string, options?: { user?: AuthenticatedUser } ): Promise { - const res = await this._update(soClient, id, {}, options?.user); + const res = await this._update(soClient, esClient, id, {}, options?.user); return res; } + public async bumpAllAgentPolicies( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, options?: { user?: AuthenticatedUser } ): Promise>> { const currentPolicies = await soClient.find({ @@ -335,7 +359,7 @@ class AgentPolicyService { await Promise.all( currentPolicies.saved_objects.map((policy) => - this.triggerAgentPolicyUpdatedEvent(soClient, 'updated', policy.id) + this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'updated', policy.id) ) ); @@ -344,6 +368,7 @@ class AgentPolicyService { public async assignPackagePolicies( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, id: string, packagePolicyIds: string[], options: { user?: AuthenticatedUser; bumpRevision: boolean } = { bumpRevision: true } @@ -356,6 +381,7 @@ class AgentPolicyService { return await this._update( soClient, + esClient, id, { package_policies: uniq( @@ -369,6 +395,7 @@ class AgentPolicyService { public async unassignPackagePolicies( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, id: string, packagePolicyIds: string[], options?: { user?: AuthenticatedUser } @@ -381,6 +408,7 @@ class AgentPolicyService { return await this._update( soClient, + esClient, id, { package_policies: uniq( @@ -409,6 +437,7 @@ class AgentPolicyService { public async delete( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, id: string ): Promise { const agentPolicy = await this.get(soClient, id, false); @@ -418,12 +447,12 @@ class AgentPolicyService { const { defaultAgentPolicy: { id: defaultAgentPolicyId }, - } = await this.ensureDefaultAgentPolicy(soClient); + } = await this.ensureDefaultAgentPolicy(soClient, esClient); if (id === defaultAgentPolicyId) { throw new Error('The default agent policy cannot be deleted'); } - const { total } = await listAgents(soClient, { + const { total } = await listAgents(soClient, esClient, { showInactive: false, perPage: 0, page: 1, @@ -435,12 +464,17 @@ class AgentPolicyService { } if (agentPolicy.package_policies && agentPolicy.package_policies.length) { - await packagePolicyService.delete(soClient, agentPolicy.package_policies as string[], { - skipUnassignFromAgentPolicies: true, - }); + await packagePolicyService.delete( + soClient, + esClient, + agentPolicy.package_policies as string[], + { + skipUnassignFromAgentPolicies: true, + } + ); } await soClient.delete(SAVED_OBJECT_TYPE, id); - await this.triggerAgentPolicyUpdatedEvent(soClient, 'deleted', id); + await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'deleted', id); return { id, name: agentPolicy.name, @@ -450,6 +484,19 @@ class AgentPolicyService { public async createFleetPolicyChangeAction( soClient: SavedObjectsClientContract, agentPolicyId: string + ) { + return appContextService.getConfig()?.agents.fleetServerEnabled + ? this.createFleetPolicyChangeFleetServer( + soClient, + appContextService.getInternalUserESClient(), + agentPolicyId + ) + : this.createFleetPolicyChangeActionSO(soClient, agentPolicyId); + } + + public async createFleetPolicyChangeActionSO( + soClient: SavedObjectsClientContract, + agentPolicyId: string ) { // If Agents is not setup skip the creation of POLICY_CHANGE agent actions // the action will be created during the fleet setup @@ -478,6 +525,38 @@ class AgentPolicyService { }); } + public async createFleetPolicyChangeFleetServer( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + agentPolicyId: string + ) { + // If Agents is not setup skip the creation of POLICY_CHANGE agent actions + // the action will be created during the fleet setup + if (!(await isAgentsSetup(soClient))) { + return; + } + const policy = await agentPolicyService.getFullAgentPolicy(soClient, agentPolicyId); + if (!policy || !policy.revision) { + return; + } + + const fleetServerPolicy: FleetServerPolicy = { + '@timestamp': new Date().toISOString(), + revision_idx: policy.revision, + coordinator_idx: 0, + data: (policy as unknown) as FleetServerPolicy['data'], + policy_id: policy.id, + default_fleet_server: false, + }; + + await esClient.create({ + index: AGENT_POLICY_INDEX, + body: fleetServerPolicy, + id: uuid(), + refresh: 'wait_for', + }); + } + public async getFullAgentPolicy( soClient: SavedObjectsClientContract, id: string, diff --git a/x-pack/plugins/fleet/server/services/agent_policy_update.ts b/x-pack/plugins/fleet/server/services/agent_policy_update.ts index fe06de765bbff..32c041b446818 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy_update.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy_update.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, SavedObjectsClientContract } from 'src/core/server'; +import { ElasticsearchClient, KibanaRequest, SavedObjectsClientContract } from 'src/core/server'; import { generateEnrollmentAPIKey, deleteEnrollmentApiKeyForAgentPolicyId } from './api_keys'; import { isAgentsSetup, unenrollForAgentPolicyId } from './agents'; import { agentPolicyService } from './agent_policy'; @@ -27,6 +27,7 @@ const fakeRequest = ({ export async function agentPolicyUpdateEventHandler( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, action: string, agentPolicyId: string ) { @@ -40,7 +41,7 @@ export async function agentPolicyUpdateEventHandler( const internalSoClient = appContextService.getInternalUserSOClient(fakeRequest); if (action === 'created') { - await generateEnrollmentAPIKey(soClient, { + await generateEnrollmentAPIKey(soClient, esClient, { agentPolicyId, }); await agentPolicyService.createFleetPolicyChangeAction(internalSoClient, agentPolicyId); @@ -51,7 +52,7 @@ export async function agentPolicyUpdateEventHandler( } if (action === 'deleted') { - await unenrollForAgentPolicyId(soClient, agentPolicyId); + await unenrollForAgentPolicyId(soClient, esClient, agentPolicyId); await deleteEnrollmentApiKeyForAgentPolicyId(soClient, agentPolicyId); } } diff --git a/x-pack/plugins/fleet/server/services/agents/acks.test.ts b/x-pack/plugins/fleet/server/services/agents/acks.test.ts index 4b09fb93e01a1..1626df4fd02ca 100644 --- a/x-pack/plugins/fleet/server/services/agents/acks.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/acks.test.ts @@ -5,7 +5,7 @@ */ import Boom from '@hapi/boom'; import { SavedObjectsBulkResponse } from 'kibana/server'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; import { Agent, @@ -19,6 +19,7 @@ import { acknowledgeAgentActions } from './acks'; describe('test agent acks services', () => { it('should succeed on valid and matched actions', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockSavedObjectsClient.bulkGet.mockReturnValue( Promise.resolve({ @@ -41,6 +42,7 @@ describe('test agent acks services', () => { await acknowledgeAgentActions( mockSavedObjectsClient, + mockElasticsearchClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, @@ -59,6 +61,7 @@ describe('test agent acks services', () => { it('should update config field on the agent if a policy change is acknowledged with an agent without policy', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const actionAttributes = { type: 'POLICY_CHANGE', @@ -85,6 +88,7 @@ describe('test agent acks services', () => { await acknowledgeAgentActions( mockSavedObjectsClient, + mockElasticsearchClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, @@ -118,6 +122,7 @@ describe('test agent acks services', () => { it('should update config field on the agent if a policy change is acknowledged with a higher revision than the agent one', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const actionAttributes = { type: 'POLICY_CHANGE', @@ -144,6 +149,7 @@ describe('test agent acks services', () => { await acknowledgeAgentActions( mockSavedObjectsClient, + mockElasticsearchClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, @@ -178,6 +184,7 @@ describe('test agent acks services', () => { it('should not update config field on the agent if a policy change is acknowledged with a lower revision than the agent one', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const actionAttributes = { type: 'POLICY_CHANGE', @@ -204,6 +211,7 @@ describe('test agent acks services', () => { await acknowledgeAgentActions( mockSavedObjectsClient, + mockElasticsearchClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, @@ -226,6 +234,7 @@ describe('test agent acks services', () => { it('should not update config field on the agent if a policy change for an old revision is acknowledged', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockSavedObjectsClient.bulkGet.mockReturnValue( Promise.resolve({ @@ -249,6 +258,7 @@ describe('test agent acks services', () => { await acknowledgeAgentActions( mockSavedObjectsClient, + mockElasticsearchClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, @@ -271,6 +281,7 @@ describe('test agent acks services', () => { it('should fail for actions that cannot be found on agent actions list', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockSavedObjectsClient.bulkGet.mockReturnValue( Promise.resolve({ saved_objects: [ @@ -288,6 +299,7 @@ describe('test agent acks services', () => { try { await acknowledgeAgentActions( mockSavedObjectsClient, + mockElasticsearchClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, @@ -310,6 +322,7 @@ describe('test agent acks services', () => { it('should fail for events that have types not in the allowed acknowledgement type list', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockSavedObjectsClient.bulkGet.mockReturnValue( Promise.resolve({ @@ -333,6 +346,7 @@ describe('test agent acks services', () => { try { await acknowledgeAgentActions( mockSavedObjectsClient, + mockElasticsearchClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, diff --git a/x-pack/plugins/fleet/server/services/agents/acks.ts b/x-pack/plugins/fleet/server/services/agents/acks.ts index 814251345788e..fab6dae0d23d5 100644 --- a/x-pack/plugins/fleet/server/services/agents/acks.ts +++ b/x-pack/plugins/fleet/server/services/agents/acks.ts @@ -5,6 +5,7 @@ */ import { + ElasticsearchClient, KibanaRequest, SavedObjectsBulkCreateObject, SavedObjectsBulkResponse, @@ -40,6 +41,7 @@ const actionCache = new LRU({ export async function acknowledgeAgentActions( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, agent: Agent, agentEvents: AgentEvent[] ): Promise { @@ -79,7 +81,7 @@ export async function acknowledgeAgentActions( const isAgentUnenrolled = actions.some((action) => action.type === 'UNENROLL'); if (isAgentUnenrolled) { - await forceUnenrollAgent(soClient, agent.id); + await forceUnenrollAgent(soClient, esClient, agent.id); } const upgradeAction = actions.find((action) => action.type === 'UPGRADE'); @@ -196,6 +198,7 @@ export async function saveAgentEvents( export interface AcksService { acknowledgeAgentActions: ( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, agent: Agent, actionIds: AgentEvent[] ) => Promise; @@ -207,6 +210,8 @@ export interface AcksService { getSavedObjectsClientContract: (kibanaRequest: KibanaRequest) => SavedObjectsClientContract; + getElasticsearchClientContract: () => ElasticsearchClient; + saveAgentEvents: ( soClient: SavedObjectsClientContract, events: AgentEvent[] diff --git a/x-pack/plugins/fleet/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts index f2cdd1f31e69f..cb893a8b88c98 100644 --- a/x-pack/plugins/fleet/server/services/agents/actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/actions.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; import { Agent, AgentAction, @@ -307,7 +307,11 @@ export async function getLatestConfigChangeAction( } export interface ActionsService { - getAgent: (soClient: SavedObjectsClientContract, agentId: string) => Promise; + getAgent: ( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + agentId: string + ) => Promise; createAgentAction: ( soClient: SavedObjectsClientContract, diff --git a/x-pack/plugins/fleet/server/services/agents/checkin/index.ts b/x-pack/plugins/fleet/server/services/agents/checkin/index.ts index 19a5c2dc08762..d9e7d9889efd9 100644 --- a/x-pack/plugins/fleet/server/services/agents/checkin/index.ts +++ b/x-pack/plugins/fleet/server/services/agents/checkin/index.ts @@ -5,7 +5,11 @@ */ import deepEqual from 'fast-deep-equal'; -import { SavedObjectsClientContract, SavedObjectsBulkCreateObject } from 'src/core/server'; +import { + ElasticsearchClient, + SavedObjectsClientContract, + SavedObjectsBulkCreateObject, +} from 'src/core/server'; import { Agent, NewAgentEvent, @@ -20,6 +24,7 @@ import { getAgentActionsForCheckin } from '../actions'; export async function agentCheckin( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, agent: Agent, data: { events: NewAgentEvent[]; @@ -54,7 +59,7 @@ export async function agentCheckin( } // Wait for new actions - actions = await agentCheckinState.subscribeToNewActions(soClient, agent, options); + actions = await agentCheckinState.subscribeToNewActions(soClient, esClient, agent, options); return { actions }; } diff --git a/x-pack/plugins/fleet/server/services/agents/checkin/state.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state.ts index 63f22b82611c2..bdbf391650bc7 100644 --- a/x-pack/plugins/fleet/server/services/agents/checkin/state.ts +++ b/x-pack/plugins/fleet/server/services/agents/checkin/state.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'src/core/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; import { Agent } from '../../../types'; import { appContextService } from '../../app_context'; import { agentCheckinStateConnectedAgentsFactory } from './state_connected_agents'; @@ -35,6 +35,7 @@ function agentCheckinStateFactory() { return { subscribeToNewActions: async ( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, agent: Agent, options?: { signal: AbortSignal } ) => { @@ -44,7 +45,7 @@ function agentCheckinStateFactory() { return agentConnected.wrapPromise( agent.id, - newActions.subscribeToNewActions(soClient, agent, options) + newActions.subscribeToNewActions(soClient, esClient, agent, options) ); }, start, diff --git a/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts index 59887d223371f..0d5394a88a87b 100644 --- a/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts @@ -21,7 +21,7 @@ import { timeout, take, } from 'rxjs/operators'; -import { SavedObjectsClientContract, KibanaRequest } from 'src/core/server'; +import { ElasticsearchClient, SavedObjectsClientContract, KibanaRequest } from 'src/core/server'; import { Agent, AgentAction, @@ -228,6 +228,7 @@ export function agentCheckinStateNewActionsFactory() { async function subscribeToNewActions( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, agent: Agent, options?: { signal: AbortSignal } ): Promise { @@ -262,7 +263,7 @@ export function agentCheckinStateNewActionsFactory() { (action) => action.type === 'INTERNAL_POLICY_REASSIGN' ); if (hasConfigReassign) { - return from(getAgent(soClient, agent.id)).pipe( + return from(getAgent(soClient, esClient, agent.id)).pipe( concatMap((refreshedAgent) => { if (!refreshedAgent.policy_id) { throw new Error('Agent does not have a policy assigned'); diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index bcd409e5f7eab..58f64c65e081d 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -4,29 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ import Boom from '@hapi/boom'; -import { SavedObjectsClientContract } from 'src/core/server'; -import { isAgentUpgradeable } from '../../../common'; -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 { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; + +import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { AgentSOAttributes, Agent, ListWithKuery } from '../../types'; +import { escapeSearchQueryPhrase } from '../saved_object'; import { savedObjectToAgent } from './saved_objects'; import { appContextService } from '../../services'; - -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); -} +import * as crudServiceSO from './crud_so'; +import * as crudServiceFleetServer from './crud_fleet_server'; export async function listAgents( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, options: ListWithKuery & { showInactive: boolean; } @@ -36,52 +26,16 @@ export async function listAgents( page: number; perPage: number; }> { - const { - page = 1, - perPage = 20, - sortField = 'enrolled_at', - sortOrder = 'desc', - kuery, - showInactive = false, - showUpgradeable, - } = options; - const filters = []; - - if (kuery && kuery !== '') { - filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); - } - - if (showInactive === false) { - filters.push(ACTIVE_AGENT_CONDITION); - } + const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled; - let { saved_objects: agentSOs, total } = await soClient.find({ - type: AGENT_SAVED_OBJECT_TYPE, - filter: _joinFilters(filters), - sortField, - sortOrder, - page, - perPage, - }); - // filtering for a range on the version string will not work, - // nor does filtering on a flattened field (local_metadata), so filter here - if (showUpgradeable) { - agentSOs = agentSOs.filter((agent) => - isAgentUpgradeable(savedObjectToAgent(agent), appContextService.getKibanaVersion()) - ); - total = agentSOs.length; - } - - return { - agents: agentSOs.map(savedObjectToAgent), - total, - page, - perPage, - }; + return fleetServerEnabled + ? crudServiceFleetServer.listAgents(esClient, options) + : crudServiceSO.listAgents(soClient, options); } export async function listAllAgents( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, options: Omit & { showInactive: boolean; } @@ -89,55 +43,34 @@ export async function listAllAgents( agents: Agent[]; total: number; }> { - const { sortField = 'enrolled_at', sortOrder = 'desc', kuery, showInactive = false } = options; - const filters = []; + const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled; - 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, - }; + return fleetServerEnabled + ? crudServiceFleetServer.listAllAgents(esClient, options) + : crudServiceSO.listAllAgents(soClient, options); } export async function countInactiveAgents( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, options: Pick ): Promise { - const { kuery } = options; - const filters = [INACTIVE_AGENT_CONDITION]; + const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled; - 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; + return fleetServerEnabled + ? crudServiceFleetServer.countInactiveAgents(esClient, options) + : crudServiceSO.countInactiveAgents(soClient, options); } -export async function getAgent(soClient: SavedObjectsClientContract, agentId: string) { - const agent = savedObjectToAgent( - await soClient.get(AGENT_SAVED_OBJECT_TYPE, agentId) - ); - return agent; +export async function getAgent( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + agentId: string +) { + const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled; + return fleetServerEnabled + ? crudServiceFleetServer.getAgent(esClient, agentId) + : crudServiceSO.getAgent(soClient, agentId); } export async function getAgents(soClient: SavedObjectsClientContract, agentIds: string[]) { @@ -187,31 +120,13 @@ export async function updateAgent( }); } -export async function deleteAgent(soClient: SavedObjectsClientContract, agentId: string) { - const agent = await getAgent(soClient, agentId); - if (agent.type === 'EPHEMERAL') { - // Delete events - let more = true; - while (more === true) { - const { saved_objects: events } = await soClient.find({ - type: AGENT_EVENT_SAVED_OBJECT_TYPE, - fields: ['id'], - search: agentId, - searchFields: ['agent_id'], - perPage: 1000, - }); - if (events.length === 0) { - more = false; - } - for (const event of events) { - await soClient.delete(AGENT_EVENT_SAVED_OBJECT_TYPE, event.id); - } - } - await soClient.delete(AGENT_SAVED_OBJECT_TYPE, agentId); - return; - } - - await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentId, { - active: false, - }); +export async function deleteAgent( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + agentId: string +) { + const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled; + return fleetServerEnabled + ? crudServiceFleetServer.deleteAgent(esClient, agentId) + : crudServiceSO.deleteAgent(soClient, agentId); } diff --git a/x-pack/plugins/fleet/server/services/agents/crud_fleet_server.ts b/x-pack/plugins/fleet/server/services/agents/crud_fleet_server.ts new file mode 100644 index 0000000000000..9c5e45c05de00 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/crud_fleet_server.ts @@ -0,0 +1,197 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import Boom from '@hapi/boom'; +import { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; + +import { isAgentUpgradeable, SO_SEARCH_LIMIT } from '../../../common'; +import { AGENT_SAVED_OBJECT_TYPE, AGENTS_INDEX } from '../../constants'; +import { ESSearchHit } from '../../../../../typings/elasticsearch'; +import { AgentSOAttributes, Agent, ListWithKuery } from '../../types'; +import { escapeSearchQueryPhrase, normalizeKuery } from '../saved_object'; +import { savedObjectToAgent } from './saved_objects'; +import { searchHitToAgent } from './helpers'; +import { appContextService } from '../../services'; + +const ACTIVE_AGENT_CONDITION = '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); +} + +function removeSOAttributes(kuery: string) { + return kuery.replace(/attributes\./g, '').replace(/fleet-agents\./g, ''); +} + +export async function listAgents( + esClient: ElasticsearchClient, + options: ListWithKuery & { + showInactive: boolean; + } +): Promise<{ + agents: Agent[]; + total: number; + page: number; + perPage: number; +}> { + const { + page = 1, + perPage = 20, + sortField = 'enrolled_at', + sortOrder = 'desc', + kuery, + showInactive = false, + showUpgradeable, + } = options; + const filters = []; + + if (kuery && kuery !== '') { + filters.push(removeSOAttributes(kuery)); + } + + if (showInactive === false) { + filters.push(ACTIVE_AGENT_CONDITION); + } + + const res = await esClient.search({ + index: AGENTS_INDEX, + from: (page - 1) * perPage, + size: perPage, + sort: `${sortField}:${sortOrder}`, + track_total_hits: true, + q: _joinFilters(filters), + }); + + let agentResults: Agent[] = res.body.hits.hits.map(searchHitToAgent); + let total = res.body.hits.total.value; + + // filtering for a range on the version string will not work, + // nor does filtering on a flattened field (local_metadata), so filter here + if (showUpgradeable) { + agentResults = agentResults.filter((agent) => + isAgentUpgradeable(agent, appContextService.getKibanaVersion()) + ); + total = agentResults.length; + } + + return { + agents: res.body.hits.hits.map(searchHitToAgent), + total, + page, + perPage, + }; +} + +export async function listAllAgents( + esClient: ElasticsearchClient, + options: Omit & { + showInactive: boolean; + } +): Promise<{ + agents: Agent[]; + total: number; +}> { + const res = await listAgents(esClient, { ...options, page: 1, perPage: SO_SEARCH_LIMIT }); + + return { + agents: res.agents, + total: res.total, + }; +} + +export async function countInactiveAgents( + esClient: ElasticsearchClient, + options: Pick +): Promise { + const { kuery } = options; + const filters = [INACTIVE_AGENT_CONDITION]; + + if (kuery && kuery !== '') { + filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); + } + + const res = await esClient.search({ + index: AGENTS_INDEX, + size: 0, + track_total_hits: true, + q: _joinFilters(filters), + }); + + return res.body.hits.total.value; +} + +export async function getAgent(esClient: ElasticsearchClient, agentId: string) { + const agentHit = await esClient.get>({ + index: AGENTS_INDEX, + id: agentId, + }); + const agent = searchHitToAgent(agentHit.body); + + 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 +): Promise { + const response = await soClient.find({ + type: AGENT_SAVED_OBJECT_TYPE, + searchFields: ['access_api_key_id'], + search: escapeSearchQueryPhrase(accessAPIKeyId), + }); + const [agent] = response.saved_objects.map(savedObjectToAgent); + + if (!agent) { + throw Boom.notFound('Agent not found'); + } + if (agent.access_api_key_id !== accessAPIKeyId) { + throw new Error('Agent api key id is not matching'); + } + if (!agent.active) { + throw Boom.forbidden('Agent inactive'); + } + + return agent; +} + +export async function updateAgent( + soClient: SavedObjectsClientContract, + agentId: string, + data: { + userProvidedMetatada: any; + } +) { + await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentId, { + user_provided_metadata: data.userProvidedMetatada, + }); +} + +export async function deleteAgent(esClient: ElasticsearchClient, agentId: string) { + await esClient.update({ + id: agentId, + index: AGENT_SAVED_OBJECT_TYPE, + body: { + active: false, + }, + }); +} diff --git a/x-pack/plugins/fleet/server/services/agents/crud_so.ts b/x-pack/plugins/fleet/server/services/agents/crud_so.ts new file mode 100644 index 0000000000000..eb8f389741a6a --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/crud_so.ts @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import Boom from '@hapi/boom'; +import { SavedObjectsClientContract } from 'src/core/server'; + +import { isAgentUpgradeable } from '../../../common'; +import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { AgentSOAttributes, Agent, ListWithKuery } from '../../types'; +import { escapeSearchQueryPhrase, normalizeKuery, findAllSOs } from '../saved_object'; +import { savedObjectToAgent } from './saved_objects'; +import { appContextService } from '../../services'; + +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, + sortField = 'enrolled_at', + sortOrder = 'desc', + kuery, + showInactive = false, + showUpgradeable, + } = options; + const filters = []; + + if (kuery && kuery !== '') { + filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); + } + + if (showInactive === false) { + filters.push(ACTIVE_AGENT_CONDITION); + } + + let { saved_objects: agentSOs, total } = await soClient.find({ + type: AGENT_SAVED_OBJECT_TYPE, + filter: _joinFilters(filters), + sortField, + sortOrder, + page, + perPage, + }); + // filtering for a range on the version string will not work, + // nor does filtering on a flattened field (local_metadata), so filter here + if (showUpgradeable) { + agentSOs = agentSOs.filter((agent) => + isAgentUpgradeable(savedObjectToAgent(agent), appContextService.getKibanaVersion()) + ); + total = agentSOs.length; + } + + return { + 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) + ); + 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 +): Promise { + const response = await soClient.find({ + type: AGENT_SAVED_OBJECT_TYPE, + searchFields: ['access_api_key_id'], + search: escapeSearchQueryPhrase(accessAPIKeyId), + }); + const [agent] = response.saved_objects.map(savedObjectToAgent); + + if (!agent) { + throw Boom.notFound('Agent not found'); + } + if (agent.access_api_key_id !== accessAPIKeyId) { + throw new Error('Agent api key id is not matching'); + } + if (!agent.active) { + throw Boom.forbidden('Agent inactive'); + } + + return agent; +} + +export async function updateAgent( + soClient: SavedObjectsClientContract, + agentId: string, + data: { + userProvidedMetatada: any; + } +) { + await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentId, { + user_provided_metadata: data.userProvidedMetatada, + }); +} + +export async function deleteAgent(soClient: SavedObjectsClientContract, agentId: string) { + await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentId, { + active: false, + }); +} diff --git a/x-pack/plugins/fleet/server/services/agents/helpers.ts b/x-pack/plugins/fleet/server/services/agents/helpers.ts new file mode 100644 index 0000000000000..38330a090ae81 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/helpers.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 { ESSearchHit } from '../../../../../typings/elasticsearch'; +import { Agent, AgentSOAttributes } from '../../types'; + +export function searchHitToAgent(hit: ESSearchHit): Agent { + return { + id: hit._id, + ...hit._source, + current_error_events: hit._source.current_error_events + ? JSON.parse(hit._source.current_error_events) + : [], + access_api_key: undefined, + status: undefined, + packages: hit._source.packages ?? [], + }; +} diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index b656ab12e96c8..8a1dc61950885 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'kibana/server'; +import { SavedObjectsClientContract, ElasticsearchClient } from 'kibana/server'; import Boom from '@hapi/boom'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { AgentSOAttributes } from '../../types'; @@ -14,6 +14,7 @@ import { createAgentAction, bulkCreateAgentActions } from './actions'; export async function reassignAgent( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, agentId: string, newAgentPolicyId: string ) { @@ -36,6 +37,7 @@ export async function reassignAgent( export async function reassignAgents( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, options: | { agentIds: string[]; @@ -55,7 +57,7 @@ export async function reassignAgents( 'agentIds' in options ? await getAgents(soClient, options.agentIds) : ( - await listAllAgents(soClient, { + await listAllAgents(soClient, esClient, { kuery: options.kuery, showInactive: false, }) diff --git a/x-pack/plugins/fleet/server/services/agents/status.test.ts b/x-pack/plugins/fleet/server/services/agents/status.test.ts index f216cd541eb21..587f0af227ff8 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; import { getAgentStatusById } from './status'; import { AGENT_TYPE_PERMANENT } from '../../../common/constants'; import { AgentSOAttributes } from '../../../common/types/models'; @@ -13,6 +13,7 @@ import { SavedObject } from 'kibana/server'; describe('Agent status service', () => { it('should return inactive when agent is not active', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockSavedObjectsClient.get = jest.fn().mockReturnValue({ id: 'id', type: AGENT_TYPE_PERMANENT, @@ -22,12 +23,13 @@ describe('Agent status service', () => { user_provided_metadata: {}, }, } as SavedObject); - const status = await getAgentStatusById(mockSavedObjectsClient, 'id'); + const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id'); expect(status).toEqual('inactive'); }); it('should return online when agent is active', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockSavedObjectsClient.get = jest.fn().mockReturnValue({ id: 'id', type: AGENT_TYPE_PERMANENT, @@ -38,12 +40,13 @@ describe('Agent status service', () => { user_provided_metadata: {}, }, } as SavedObject); - const status = await getAgentStatusById(mockSavedObjectsClient, 'id'); + const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id'); expect(status).toEqual('online'); }); it('should return enrolling when agent is active but never checkin', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockSavedObjectsClient.get = jest.fn().mockReturnValue({ id: 'id', type: AGENT_TYPE_PERMANENT, @@ -53,12 +56,13 @@ describe('Agent status service', () => { user_provided_metadata: {}, }, } as SavedObject); - const status = await getAgentStatusById(mockSavedObjectsClient, 'id'); + const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id'); expect(status).toEqual('enrolling'); }); it('should return unenrolling when agent is unenrolling', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockSavedObjectsClient.get = jest.fn().mockReturnValue({ id: 'id', type: AGENT_TYPE_PERMANENT, @@ -70,7 +74,7 @@ describe('Agent status service', () => { user_provided_metadata: {}, }, } as SavedObject); - const status = await getAgentStatusById(mockSavedObjectsClient, 'id'); + const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id'); expect(status).toEqual('unenrolling'); }); }); diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index 74faedc8e2931..ba8f8fc363857 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'src/core/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; import pMap from 'p-map'; import { getAgent, listAgents } from './crud'; import { AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../constants'; @@ -14,9 +14,10 @@ import { AgentStatusKueryHelper } from '../../../common/services'; export async function getAgentStatusById( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, agentId: string ): Promise { - const agent = await getAgent(soClient, agentId); + const agent = await getAgent(soClient, esClient, agentId); return AgentStatusKueryHelper.getAgentStatus(agent); } @@ -36,6 +37,7 @@ function joinKuerys(...kuerys: Array) { export async function getAgentStatusForAgentPolicy( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, agentPolicyId?: string, filterKuery?: string ) { @@ -48,7 +50,7 @@ export async function getAgentStatusForAgentPolicy( AgentStatusKueryHelper.buildKueryForUpdatingAgents(), ], (kuery) => - listAgents(soClient, { + listAgents(soClient, esClient, { showInactive: false, perPage: 0, page: 1, diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.ts index 9c2b2bdfe7f6d..5246927cb4ee4 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.ts @@ -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 { SavedObjectsClientContract } from 'src/core/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; import { AgentSOAttributes } from '../../types'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { getAgent } from './crud'; @@ -25,6 +25,7 @@ export async function unenrollAgent(soClient: SavedObjectsClientContract, agentI export async function unenrollAgents( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, options: | { agentIds: string[]; @@ -38,7 +39,7 @@ export async function unenrollAgents( 'agentIds' in options ? await getAgents(soClient, options.agentIds) : ( - await listAllAgents(soClient, { + await listAllAgents(soClient, esClient, { kuery: options.kuery, showInactive: false, }) @@ -70,8 +71,12 @@ export async function unenrollAgents( ); } -export async function forceUnenrollAgent(soClient: SavedObjectsClientContract, agentId: string) { - const agent = await getAgent(soClient, agentId); +export async function forceUnenrollAgent( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + agentId: string +) { + const agent = await getAgent(soClient, esClient, agentId); await Promise.all([ agent.access_api_key_id @@ -90,6 +95,7 @@ export async function forceUnenrollAgent(soClient: SavedObjectsClientContract, a export async function forceUnenrollAgents( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, options: | { agentIds: string[]; @@ -103,7 +109,7 @@ export async function forceUnenrollAgents( 'agentIds' in options ? await getAgents(soClient, options.agentIds) : ( - await listAllAgents(soClient, { + await listAllAgents(soClient, esClient, { kuery: options.kuery, showInactive: false, }) diff --git a/x-pack/plugins/fleet/server/services/agents/update.ts b/x-pack/plugins/fleet/server/services/agents/update.ts index b85a831294b58..7bd807bf4e575 100644 --- a/x-pack/plugins/fleet/server/services/agents/update.ts +++ b/x-pack/plugins/fleet/server/services/agents/update.ts @@ -4,19 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'src/core/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; import { listAgents } from './crud'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { unenrollAgent } from './unenroll'; export async function unenrollForAgentPolicyId( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, policyId: string ) { let hasMore = true; let page = 1; while (hasMore) { - const { agents } = await listAgents(soClient, { + const { agents } = await listAgents(soClient, esClient, { kuery: `${AGENT_SAVED_OBJECT_TYPE}.policy_id:"${policyId}"`, page: page++, perPage: 1000, diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index cf83a938d3c39..9515cca8ce007 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'src/core/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; import { AgentSOAttributes, AgentAction, AgentActionSOAttributes } from '../../types'; import { AGENT_ACTION_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { bulkCreateAgentActions, createAgentAction } from './actions'; @@ -59,6 +59,7 @@ export async function ackAgentUpgraded( export async function sendUpgradeAgentsActions( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, options: | { agentIds: string[]; @@ -79,7 +80,7 @@ export async function sendUpgradeAgentsActions( 'agentIds' in options ? await getAgents(soClient, options.agentIds) : ( - await listAllAgents(soClient, { + await listAllAgents(soClient, esClient, { kuery: options.kuery, showInactive: false, }) diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts index 8f67753392e65..747cbae3f71ce 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts @@ -4,18 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import uuid from 'uuid'; -import Boom from '@hapi/boom'; -import { SavedObjectsClientContract, SavedObject } from 'src/core/server'; -import { EnrollmentAPIKey, EnrollmentAPIKeySOAttributes } from '../../types'; -import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../constants'; -import { createAPIKey, invalidateAPIKeys } from './security'; -import { agentPolicyService } from '../agent_policy'; +import { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; +import { EnrollmentAPIKey } from '../../types'; import { appContextService } from '../app_context'; -import { normalizeKuery } from '../saved_object'; +import * as enrollmentApiKeyServiceSO from './enrollment_api_key_so'; +import * as enrollmentApiKeyServiceFleetServer from './enrollment_api_key_fleet_server'; export async function listEnrollmentApiKeys( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, options: { page?: number; perPage?: number; @@ -23,39 +20,23 @@ export async function listEnrollmentApiKeys( showInactive?: boolean; } ): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> { - const { page = 1, perPage = 20, kuery } = options; - - // eslint-disable-next-line @typescript-eslint/naming-convention - const { saved_objects, total } = await soClient.find({ - type: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, - page, - perPage, - sortField: 'created_at', - sortOrder: 'desc', - filter: - kuery && kuery !== '' - ? normalizeKuery(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, kuery) - : undefined, - }); - - const items = saved_objects.map(savedObjectToEnrollmentApiKey); - - return { - items, - total, - page, - perPage, - }; + if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) { + return enrollmentApiKeyServiceFleetServer.listEnrollmentApiKeys(esClient, options); + } else { + return enrollmentApiKeyServiceSO.listEnrollmentApiKeys(soClient, options); + } } -export async function getEnrollmentAPIKey(soClient: SavedObjectsClientContract, id: string) { - const so = await appContextService - .getEncryptedSavedObjects() - .getDecryptedAsInternalUser( - ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, - id - ); - return savedObjectToEnrollmentApiKey(so); +export async function getEnrollmentAPIKey( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + id: string +) { + if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) { + return enrollmentApiKeyServiceFleetServer.getEnrollmentAPIKey(esClient, id); + } else { + return enrollmentApiKeyServiceSO.getEnrollmentAPIKey(soClient, id); + } } /** @@ -63,112 +44,37 @@ export async function getEnrollmentAPIKey(soClient: SavedObjectsClientContract, * @param soClient * @param id */ -export async function deleteEnrollmentApiKey(soClient: SavedObjectsClientContract, id: string) { - const enrollmentApiKey = await getEnrollmentAPIKey(soClient, id); - - await invalidateAPIKeys(soClient, [enrollmentApiKey.api_key_id]); - - await soClient.update(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, id, { - active: false, - }); +export async function deleteEnrollmentApiKey( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + id: string +) { + if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) { + return enrollmentApiKeyServiceFleetServer.deleteEnrollmentApiKey(soClient, esClient, id); + } else { + return enrollmentApiKeyServiceSO.deleteEnrollmentApiKey(soClient, id); + } } export async function deleteEnrollmentApiKeyForAgentPolicyId( soClient: SavedObjectsClientContract, agentPolicyId: string ) { - let hasMore = true; - let page = 1; - while (hasMore) { - const { items } = await listEnrollmentApiKeys(soClient, { - page: page++, - perPage: 100, - kuery: `${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}.policy_id:${agentPolicyId}`, - }); - - if (items.length === 0) { - hasMore = false; - } - - for (const apiKey of items) { - await deleteEnrollmentApiKey(soClient, apiKey.id); - } - } + return enrollmentApiKeyServiceSO.deleteEnrollmentApiKeyForAgentPolicyId(soClient, agentPolicyId); } export async function generateEnrollmentAPIKey( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, data: { name?: string; expiration?: string; agentPolicyId?: string; } ) { - const id = uuid.v4(); - const { name: providedKeyName } = data; - if (data.agentPolicyId) { - await validateAgentPolicyId(soClient, data.agentPolicyId); - } - const agentPolicyId = - data.agentPolicyId ?? (await agentPolicyService.getDefaultAgentPolicyId(soClient)); - const name = providedKeyName ? `${providedKeyName} (${id})` : id; - const key = await createAPIKey(soClient, name, { - // Useless role to avoid to have the privilege of the user that created the key - 'fleet-apikey-enroll': { - cluster: [], - applications: [ - { - application: '.fleet', - privileges: ['no-privileges'], - resources: ['*'], - }, - ], - }, - }); - - if (!key) { - throw new Error('Unable to create an enrollment api key'); + if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) { + return enrollmentApiKeyServiceFleetServer.generateEnrollmentAPIKey(soClient, esClient, data); + } else { + return enrollmentApiKeyServiceSO.generateEnrollmentAPIKey(soClient, data); } - - const apiKey = Buffer.from(`${key.id}:${key.api_key}`).toString('base64'); - - const so = await soClient.create( - ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, - { - active: true, - api_key_id: key.id, - api_key: apiKey, - name, - policy_id: agentPolicyId, - created_at: new Date().toISOString(), - } - ); - - return getEnrollmentAPIKey(soClient, so.id); -} - -async function validateAgentPolicyId(soClient: SavedObjectsClientContract, agentPolicyId: string) { - try { - await agentPolicyService.get(soClient, agentPolicyId); - } catch (e) { - if (e.isBoom && e.output.statusCode === 404) { - throw Boom.badRequest(`Agent policy ${agentPolicyId} does not exist`); - } - throw e; - } -} - -function savedObjectToEnrollmentApiKey({ - error, - attributes, - id, -}: SavedObject): EnrollmentAPIKey { - if (error) { - throw new Error(error.message); - } - - return { - id, - ...attributes, - }; } diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_fleet_server.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_fleet_server.ts new file mode 100644 index 0000000000000..c0aa42c6e4ed8 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_fleet_server.ts @@ -0,0 +1,205 @@ +/* + * 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 uuid from 'uuid'; +import Boom from '@hapi/boom'; +import { ResponseError } from '@elastic/elasticsearch/lib/errors'; +import { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; +import { EnrollmentAPIKey, FleetServerEnrollmentAPIKey } from '../../types'; +import { ENROLLMENT_API_KEYS_INDEX } from '../../constants'; +import { createAPIKey, invalidateAPIKeys } from './security'; +import { agentPolicyService } from '../agent_policy'; + +// TODO Move these types to another file +interface SearchResponse { + took: number; + timed_out: boolean; + _scroll_id?: string; + hits: { + total: { + value: number; + relation: string; + }; + max_score: number; + hits: Array<{ + _index: string; + _type: string; + _id: string; + _score: number; + _source: T; + _version?: number; + fields?: any; + highlight?: any; + inner_hits?: any; + matched_queries?: string[]; + sort?: string[]; + }>; + }; +} + +type SearchHit = SearchResponse['hits']['hits'][0]; + +export async function listEnrollmentApiKeys( + esClient: ElasticsearchClient, + options: { + page?: number; + perPage?: number; + kuery?: string; + showInactive?: boolean; + } +): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> { + const { page = 1, perPage = 20, kuery } = options; + + const res = await esClient.search>({ + index: ENROLLMENT_API_KEYS_INDEX, + from: (page - 1) * perPage, + size: perPage, + sort: 'created_at:desc', + track_total_hits: true, + q: kuery, + }); + + const items = res.body.hits.hits.map(esDocToEnrollmentApiKey); + + return { + items, + total: res.body.hits.total.value, + page, + perPage, + }; +} + +export async function getEnrollmentAPIKey( + esClient: ElasticsearchClient, + id: string +): Promise { + try { + const res = await esClient.get>({ + index: ENROLLMENT_API_KEYS_INDEX, + id, + }); + + return esDocToEnrollmentApiKey(res.body); + } catch (e) { + if (e instanceof ResponseError && e.statusCode === 404) { + throw Boom.notFound(`Enrollment api key ${id} not found`); + } + + throw e; + } +} + +/** + * Invalidate an api key and mark it as inactive + * @param soClient + * @param id + */ +export async function deleteEnrollmentApiKey( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + id: string +) { + const enrollmentApiKey = await getEnrollmentAPIKey(esClient, id); + + await invalidateAPIKeys(soClient, [enrollmentApiKey.api_key_id]); + + await esClient.update({ + index: ENROLLMENT_API_KEYS_INDEX, + id, + body: { + doc: { + active: false, + }, + }, + refresh: 'wait_for', + }); +} + +export async function deleteEnrollmentApiKeyForAgentPolicyId( + soClient: SavedObjectsClientContract, + agentPolicyId: string +) { + throw new Error('NOT IMPLEMENTED'); +} + +export async function generateEnrollmentAPIKey( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + data: { + name?: string; + expiration?: string; + agentPolicyId?: string; + } +): Promise { + const id = uuid.v4(); + const { name: providedKeyName } = data; + if (data.agentPolicyId) { + await validateAgentPolicyId(soClient, data.agentPolicyId); + } + const agentPolicyId = + data.agentPolicyId ?? (await agentPolicyService.getDefaultAgentPolicyId(soClient)); + const name = providedKeyName ? `${providedKeyName} (${id})` : id; + const key = await createAPIKey(soClient, name, { + // Useless role to avoid to have the privilege of the user that created the key + 'fleet-apikey-enroll': { + cluster: [], + applications: [ + { + application: '.fleet', + privileges: ['no-privileges'], + resources: ['*'], + }, + ], + }, + }); + + if (!key) { + throw new Error('Unable to create an enrollment api key'); + } + + const apiKey = Buffer.from(`${key.id}:${key.api_key}`).toString('base64'); + + const body = { + active: true, + api_key_id: key.id, + api_key: apiKey, + name, + policy_id: agentPolicyId, + created_at: new Date().toISOString(), + }; + + const res = await esClient.create({ + index: ENROLLMENT_API_KEYS_INDEX, + body, + id, + refresh: 'wait_for', + }); + + return { + id: res.body._id, + ...body, + }; +} + +async function validateAgentPolicyId(soClient: SavedObjectsClientContract, agentPolicyId: string) { + try { + await agentPolicyService.get(soClient, agentPolicyId); + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + throw Boom.badRequest(`Agent policy ${agentPolicyId} does not exist`); + } + throw e; + } +} + +function esDocToEnrollmentApiKey(doc: SearchHit): EnrollmentAPIKey { + return { + id: doc._id, + ...doc._source, + created_at: doc._source.created_at as string, + active: doc._source.active || false, + }; +} diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_so.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_so.ts new file mode 100644 index 0000000000000..8f67753392e65 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_so.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import uuid from 'uuid'; +import Boom from '@hapi/boom'; +import { SavedObjectsClientContract, SavedObject } from 'src/core/server'; +import { EnrollmentAPIKey, EnrollmentAPIKeySOAttributes } from '../../types'; +import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../constants'; +import { createAPIKey, invalidateAPIKeys } from './security'; +import { agentPolicyService } from '../agent_policy'; +import { appContextService } from '../app_context'; +import { normalizeKuery } from '../saved_object'; + +export async function listEnrollmentApiKeys( + soClient: SavedObjectsClientContract, + options: { + page?: number; + perPage?: number; + kuery?: string; + showInactive?: boolean; + } +): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> { + const { page = 1, perPage = 20, kuery } = options; + + // eslint-disable-next-line @typescript-eslint/naming-convention + const { saved_objects, total } = await soClient.find({ + type: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + page, + perPage, + sortField: 'created_at', + sortOrder: 'desc', + filter: + kuery && kuery !== '' + ? normalizeKuery(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, kuery) + : undefined, + }); + + const items = saved_objects.map(savedObjectToEnrollmentApiKey); + + return { + items, + total, + page, + perPage, + }; +} + +export async function getEnrollmentAPIKey(soClient: SavedObjectsClientContract, id: string) { + const so = await appContextService + .getEncryptedSavedObjects() + .getDecryptedAsInternalUser( + ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + id + ); + return savedObjectToEnrollmentApiKey(so); +} + +/** + * Invalidate an api key and mark it as inactive + * @param soClient + * @param id + */ +export async function deleteEnrollmentApiKey(soClient: SavedObjectsClientContract, id: string) { + const enrollmentApiKey = await getEnrollmentAPIKey(soClient, id); + + await invalidateAPIKeys(soClient, [enrollmentApiKey.api_key_id]); + + await soClient.update(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, id, { + active: false, + }); +} + +export async function deleteEnrollmentApiKeyForAgentPolicyId( + soClient: SavedObjectsClientContract, + agentPolicyId: string +) { + let hasMore = true; + let page = 1; + while (hasMore) { + const { items } = await listEnrollmentApiKeys(soClient, { + page: page++, + perPage: 100, + kuery: `${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}.policy_id:${agentPolicyId}`, + }); + + if (items.length === 0) { + hasMore = false; + } + + for (const apiKey of items) { + await deleteEnrollmentApiKey(soClient, apiKey.id); + } + } +} + +export async function generateEnrollmentAPIKey( + soClient: SavedObjectsClientContract, + data: { + name?: string; + expiration?: string; + agentPolicyId?: string; + } +) { + const id = uuid.v4(); + const { name: providedKeyName } = data; + if (data.agentPolicyId) { + await validateAgentPolicyId(soClient, data.agentPolicyId); + } + const agentPolicyId = + data.agentPolicyId ?? (await agentPolicyService.getDefaultAgentPolicyId(soClient)); + const name = providedKeyName ? `${providedKeyName} (${id})` : id; + const key = await createAPIKey(soClient, name, { + // Useless role to avoid to have the privilege of the user that created the key + 'fleet-apikey-enroll': { + cluster: [], + applications: [ + { + application: '.fleet', + privileges: ['no-privileges'], + resources: ['*'], + }, + ], + }, + }); + + if (!key) { + throw new Error('Unable to create an enrollment api key'); + } + + const apiKey = Buffer.from(`${key.id}:${key.api_key}`).toString('base64'); + + const so = await soClient.create( + ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + { + active: true, + api_key_id: key.id, + api_key: apiKey, + name, + policy_id: agentPolicyId, + created_at: new Date().toISOString(), + } + ); + + return getEnrollmentAPIKey(soClient, so.id); +} + +async function validateAgentPolicyId(soClient: SavedObjectsClientContract, agentPolicyId: string) { + try { + await agentPolicyService.get(soClient, agentPolicyId); + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + throw Boom.badRequest(`Agent policy ${agentPolicyId} does not exist`); + } + throw e; + } +} + +function savedObjectToEnrollmentApiKey({ + error, + attributes, + id, +}: SavedObject): EnrollmentAPIKey { + if (error) { + throw new Error(error.message); + } + + return { + id, + ...attributes, + }; +} diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index d6b62458ed1f4..66ffd3ca53081 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -5,7 +5,13 @@ */ import { BehaviorSubject, Observable } from 'rxjs'; import { first } from 'rxjs/operators'; -import { SavedObjectsServiceStart, HttpServiceSetup, Logger, KibanaRequest } from 'src/core/server'; +import { + ElasticsearchClient, + SavedObjectsServiceStart, + HttpServiceSetup, + Logger, + KibanaRequest, +} from 'src/core/server'; import { EncryptedSavedObjectsClient, EncryptedSavedObjectsPluginSetup, @@ -19,6 +25,7 @@ import { CloudSetup } from '../../../cloud/server'; class AppContextService { private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined; private encryptedSavedObjectsSetup: EncryptedSavedObjectsPluginSetup | undefined; + private esClient: ElasticsearchClient | undefined; private security: SecurityPluginStart | undefined; private config$?: Observable; private configSubject$?: BehaviorSubject; @@ -32,6 +39,7 @@ class AppContextService { private externalCallbacks: ExternalCallbacksStorage = new Map(); public async start(appContext: FleetAppContext) { + this.esClient = appContext.elasticsearch.client.asInternalUser; this.encryptedSavedObjects = appContext.encryptedSavedObjectsStart?.getClient(); this.encryptedSavedObjectsSetup = appContext.encryptedSavedObjectsSetup; this.security = appContext.security; @@ -96,12 +104,20 @@ class AppContextService { } public getInternalUserSOClient(request: KibanaRequest) { - // soClient as kibana internal users, be carefull on how you use it, security is not enabled + // soClient as kibana internal users, be careful on how you use it, security is not enabled return appContextService.getSavedObjects().getScopedClient(request, { excludedWrappers: ['security'], }); } + public getInternalUserESClient() { + if (!this.esClient) { + throw new Error('Elasticsearch start service not set.'); + } + // soClient as kibana internal users, be careful on how you use it, security is not enabled + return this.esClient; + } + public getIsProductionMode() { return this.isProductionMode; } diff --git a/x-pack/plugins/fleet/server/services/fleet_server_migration.ts b/x-pack/plugins/fleet/server/services/fleet_server_migration.ts new file mode 100644 index 0000000000000..1a50b5c9df767 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server_migration.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaRequest } from 'src/core/server'; +import { + ENROLLMENT_API_KEYS_INDEX, + ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + FleetServerEnrollmentAPIKey, +} from '../../common'; +import { listEnrollmentApiKeys, getEnrollmentAPIKey } from './api_keys/enrollment_api_key_so'; +import { appContextService } from './app_context'; + +export async function runFleetServerMigration() { + const logger = appContextService.getLogger(); + logger.info('Starting fleet server migration'); + await migrateEnrollmentApiKeys(); + logger.info('Fleet server migration finished'); +} + +function getInternalUserSOClient() { + const fakeRequest = ({ + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + } as unknown) as KibanaRequest; + + return appContextService.getInternalUserSOClient(fakeRequest); +} + +async function migrateEnrollmentApiKeys() { + const esClient = appContextService.getInternalUserESClient(); + const soClient = getInternalUserSOClient(); + let hasMore = true; + while (hasMore) { + const res = await listEnrollmentApiKeys(soClient, { + page: 1, + perPage: 100, + }); + if (res.total === 0) { + hasMore = false; + } + for (const item of res.items) { + const key = await getEnrollmentAPIKey(soClient, item.id); + + const body: FleetServerEnrollmentAPIKey = { + api_key: key.api_key, + api_key_id: key.api_key_id, + active: key.active, + created_at: key.created_at, + name: key.name, + policy_id: key.policy_id, + }; + await esClient.create({ + index: ENROLLMENT_API_KEYS_INDEX, + body, + id: key.id, + refresh: 'wait_for', + }); + + await soClient.delete(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, key.id); + } + } +} diff --git a/x-pack/plugins/fleet/server/services/index.ts b/x-pack/plugins/fleet/server/services/index.ts index d9015c5195536..b590b2ed002c0 100644 --- a/x-pack/plugins/fleet/server/services/index.ts +++ b/x-pack/plugins/fleet/server/services/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract, KibanaRequest } from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClientContract, KibanaRequest } from 'kibana/server'; import { AgentStatus, Agent, EsAssetReference } from '../types'; import * as settingsService from './settings'; import { getAgent, listAgents } from './agents'; @@ -53,7 +53,11 @@ export interface AgentService { /** * Return the status by the Agent's id */ - getAgentStatusById(soClient: SavedObjectsClientContract, agentId: string): Promise; + getAgentStatusById( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + agentId: string + ): Promise; /** * List agents */ diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index 5e295c1576705..eb26b405fbdab 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; import { createPackagePolicyMock } from '../../common/mocks'; import { packagePolicyService } from './package_policy'; import { PackageInfo, PackagePolicySOAttributes } from '../types'; @@ -345,9 +345,11 @@ describe('Package policy service', () => { throw savedObjectsClient.errors.createConflictError('abc', '123'); } ); + const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; await expect( packagePolicyService.update( savedObjectsClient, + elasticsearchClient, 'the-package-policy-id', createPackagePolicyMock() ) diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 95b1a43ec2e5e..605b0f6cf65cc 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -3,7 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, RequestHandlerContext, SavedObjectsClientContract } from 'src/core/server'; +import { + ElasticsearchClient, + KibanaRequest, + RequestHandlerContext, + SavedObjectsClientContract, +} from 'src/core/server'; import uuid from 'uuid'; import { AuthenticatedUser } from '../../../security/server'; import { @@ -47,6 +52,7 @@ function getDataset(st: string) { class PackagePolicyService { public async create( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, callCluster: CallESAsCurrentUser, packagePolicy: NewPackagePolicy, options?: { id?: string; user?: AuthenticatedUser; bumpRevision?: boolean } @@ -116,10 +122,16 @@ class PackagePolicyService { ); // Assign it to the given agent policy - await agentPolicyService.assignPackagePolicies(soClient, packagePolicy.policy_id, [newSo.id], { - user: options?.user, - bumpRevision: options?.bumpRevision ?? true, - }); + await agentPolicyService.assignPackagePolicies( + soClient, + esClient, + packagePolicy.policy_id, + [newSo.id], + { + user: options?.user, + bumpRevision: options?.bumpRevision ?? true, + } + ); return { id: newSo.id, @@ -130,6 +142,7 @@ class PackagePolicyService { public async bulkCreate( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, packagePolicies: NewPackagePolicy[], agentPolicyId: string, options?: { user?: AuthenticatedUser; bumpRevision?: boolean } @@ -167,6 +180,7 @@ class PackagePolicyService { // Assign it to the given agent policy await agentPolicyService.assignPackagePolicies( soClient, + esClient, agentPolicyId, newSos.map((newSo) => newSo.id), { @@ -252,6 +266,7 @@ class PackagePolicyService { public async update( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, id: string, packagePolicy: UpdatePackagePolicy, options?: { user?: AuthenticatedUser } @@ -308,7 +323,7 @@ class PackagePolicyService { ); // Bump revision of associated agent policy - await agentPolicyService.bumpRevision(soClient, packagePolicy.policy_id, { + await agentPolicyService.bumpRevision(soClient, esClient, packagePolicy.policy_id, { user: options?.user, }); @@ -317,6 +332,7 @@ class PackagePolicyService { public async delete( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, ids: string[], options?: { user?: AuthenticatedUser; skipUnassignFromAgentPolicies?: boolean } ): Promise { @@ -331,6 +347,7 @@ class PackagePolicyService { if (!options?.skipUnassignFromAgentPolicies) { await agentPolicyService.unassignPackagePolicies( soClient, + esClient, packagePolicy.policy_id, [packagePolicy.id], { diff --git a/x-pack/plugins/fleet/server/services/setup.test.ts b/x-pack/plugins/fleet/server/services/setup.test.ts index bb01862aaf317..1e2440ba3b546 100644 --- a/x-pack/plugins/fleet/server/services/setup.test.ts +++ b/x-pack/plugins/fleet/server/services/setup.test.ts @@ -41,8 +41,9 @@ describe('setupIngestManager', () => { soClient.find = mockedMethodThrowsError(); soClient.get = mockedMethodThrowsError(); soClient.update = mockedMethodThrowsError(); + const esClient = context.core.elasticsearch.client.asCurrentUser; - const setupPromise = setupIngestManager(soClient, jest.fn()); + const setupPromise = setupIngestManager(soClient, esClient, jest.fn()); await expect(setupPromise).rejects.toThrow('SO method mocked to throw'); await expect(setupPromise).rejects.toThrow(Error); }); @@ -53,8 +54,9 @@ describe('setupIngestManager', () => { soClient.find = mockedMethodThrowsCustom(); soClient.get = mockedMethodThrowsCustom(); soClient.update = mockedMethodThrowsCustom(); + const esClient = context.core.elasticsearch.client.asCurrentUser; - const setupPromise = setupIngestManager(soClient, jest.fn()); + const setupPromise = setupIngestManager(soClient, esClient, jest.fn()); await expect(setupPromise).rejects.toThrow('method mocked to throw'); await expect(setupPromise).rejects.toThrow(CustomTestError); }); diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index c37eed1910883..1ce7b1d85c8e4 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -5,7 +5,7 @@ */ import uuid from 'uuid'; -import { SavedObjectsClientContract } from 'src/core/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; import { CallESAsCurrentUser } from '../types'; import { agentPolicyService } from './agent_policy'; import { outputService } from './output'; @@ -39,13 +39,15 @@ export interface SetupStatus { export async function setupIngestManager( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, callCluster: CallESAsCurrentUser ): Promise { - return awaitIfPending(async () => createSetupSideEffects(soClient, callCluster)); + return awaitIfPending(async () => createSetupSideEffects(soClient, esClient, callCluster)); } async function createSetupSideEffects( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, callCluster: CallESAsCurrentUser ): Promise { const [ @@ -56,7 +58,7 @@ async function createSetupSideEffects( // packages installed by default ensureInstalledDefaultPackages(soClient, callCluster), outputService.ensureDefaultOutput(soClient), - agentPolicyService.ensureDefaultAgentPolicy(soClient), + agentPolicyService.ensureDefaultAgentPolicy(soClient, esClient), settingsService.getSettings(soClient).catch((e: any) => { if (e.isBoom && e.output.statusCode === 404) { const defaultSettings = createDefaultSettings(); @@ -109,6 +111,7 @@ async function createSetupSideEffects( if (!isInstalled) { await addPackageToAgentPolicy( soClient, + esClient, callCluster, installedPackage, agentPolicyWithPackagePolicies, @@ -125,6 +128,7 @@ async function createSetupSideEffects( export async function setupFleet( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, callCluster: CallESAsCurrentUser, options?: { forceRecreate?: boolean } ) { @@ -189,7 +193,7 @@ export async function setupFleet( await Promise.all( agentPolicies.map((agentPolicy) => { - return generateEnrollmentAPIKey(soClient, { + return generateEnrollmentAPIKey(soClient, esClient, { name: `Default`, agentPolicyId: agentPolicy.id, }); @@ -209,6 +213,7 @@ function generateRandomPassword() { async function addPackageToAgentPolicy( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, callCluster: CallESAsCurrentUser, packageToInstall: Installation, agentPolicy: AgentPolicy, @@ -227,7 +232,7 @@ async function addPackageToAgentPolicy( agentPolicy.namespace ); - await packagePolicyService.create(soClient, callCluster, newPackagePolicy, { + await packagePolicyService.create(soClient, esClient, callCluster, newPackagePolicy, { bumpRevision: false, }); } diff --git a/x-pack/plugins/fleet/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx index 7e6e6d5e408b4..d3ac402159178 100644 --- a/x-pack/plugins/fleet/server/types/index.tsx +++ b/x-pack/plugins/fleet/server/types/index.tsx @@ -77,6 +77,8 @@ export { PostAgentCheckinRequest, DataType, dataTypes, + // Fleet Server types + FleetServerEnrollmentAPIKey, } from '../../common'; export type CallESAsCurrentUser = LegacyScopedClusterClient['callAsCurrentUser']; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts index 5773b88fa2bea..225592fa8e686 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts @@ -5,7 +5,11 @@ */ import { Subject } from 'rxjs'; -import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks'; +import { + elasticsearchServiceMock, + loggingSystemMock, + savedObjectsServiceMock, +} from 'src/core/server/mocks'; import { LicenseService } from '../../../../common/license/license'; import { createPackagePolicyServiceMock } from '../../../../../fleet/server/mocks'; import { PolicyWatcher } from './license_watch'; @@ -31,6 +35,7 @@ const MockPPWithEndpointPolicy = (cb?: (p: PolicyConfig) => PolicyConfig): Packa describe('Policy-Changing license watcher', () => { const logger = loggingSystemMock.create().get('license_watch.test'); const soStartMock = savedObjectsServiceMock.createStartContract(); + const esStartMock = elasticsearchServiceMock.createStart(); let packagePolicySvcMock: jest.Mocked; const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: 'platinum' } }); @@ -45,7 +50,7 @@ describe('Policy-Changing license watcher', () => { // mock a license-changing service to test reactivity const licenseEmitter: Subject = new Subject(); const licenseService = new LicenseService(); - const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, logger); + const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, esStartMock, logger); // swap out watch function, just to ensure it gets called when a license change happens const mockWatch = jest.fn(); @@ -90,7 +95,7 @@ describe('Policy-Changing license watcher', () => { perPage: 100, }); - const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, logger); + const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, esStartMock, logger); await pw.watch(Gold); // just manually trigger with a given license expect(packagePolicySvcMock.list.mock.calls.length).toBe(3); // should have asked for 3 pages of resuts @@ -119,14 +124,14 @@ describe('Policy-Changing license watcher', () => { perPage: 100, }); - const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, logger); + const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, esStartMock, logger); // emulate a license change below paid tier await pw.watch(Basic); expect(packagePolicySvcMock.update).toHaveBeenCalled(); expect( - packagePolicySvcMock.update.mock.calls[0][2].inputs[0].config!.policy.value.windows.popup + packagePolicySvcMock.update.mock.calls[0][3].inputs[0].config!.policy.value.windows.popup .malware.message ).not.toEqual(CustomMessage); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts index 2f0c3bf8fd5ba..a8aa0f25b0782 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts @@ -7,6 +7,8 @@ import { Subscription } from 'rxjs'; import { + ElasticsearchClient, + ElasticsearchServiceStart, KibanaRequest, Logger, SavedObjectsClientContract, @@ -28,15 +30,18 @@ import { isAtLeast, LicenseService } from '../../../../common/license/license'; export class PolicyWatcher { private logger: Logger; private soClient: SavedObjectsClientContract; + private esClient: ElasticsearchClient; private policyService: PackagePolicyServiceInterface; private subscription: Subscription | undefined; constructor( policyService: PackagePolicyServiceInterface, soStart: SavedObjectsServiceStart, + esStart: ElasticsearchServiceStart, logger: Logger ) { this.policyService = policyService; this.soClient = this.makeInternalSOClient(soStart); + this.esClient = esStart.client.asInternalUser; this.logger = logger; } @@ -113,11 +118,16 @@ export class PolicyWatcher { license ); try { - await this.policyService.update(this.soClient, policy.id, updatePolicy); + await this.policyService.update(this.soClient, this.esClient, policy.id, updatePolicy); } catch (e) { // try again for transient issues try { - await this.policyService.update(this.soClient, policy.id, updatePolicy); + await this.policyService.update( + this.soClient, + this.esClient, + policy.id, + updatePolicy + ); } catch (ee) { this.logger.warn( `Unable to remove platinum features from policy ${policy.id}: ${ee.message}` diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index a79175b178c38..83732170fb5c3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -68,13 +68,15 @@ export const getMetadataListRequestHandler = function ( const unenrolledAgentIds = await findAllUnenrolledAgentIds( agentService, - context.core.savedObjects.client + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser ); const statusIDs = request?.body?.filters?.host_status?.length ? await findAgentIDsByStatus( agentService, context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser, request.body?.filters?.host_status ) : undefined; @@ -193,6 +195,7 @@ async function findAgent( ?.getAgentService() ?.getAgent( metadataRequestContext.requestHandlerContext.core.savedObjects.client, + metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser, hostMetadata.elastic.agent.id ); } catch (e) { @@ -267,6 +270,7 @@ export async function enrichHostMetadata( ?.getAgentService() ?.getAgentStatusById( metadataRequestContext.requestHandlerContext.core.savedObjects.client, + metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser, elasticAgentId ); hostStatus = HOST_STATUS_MAPPING.get(status!) || HostStatus.ERROR; @@ -289,6 +293,7 @@ export async function enrichHostMetadata( ?.getAgentService() ?.getAgent( metadataRequestContext.requestHandlerContext.core.savedObjects.client, + metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser, elasticAgentId ); const agentPolicy = await metadataRequestContext.endpointAppContextService diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts index e9a1f1e24fa55..d7fe8b75cbc81 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; import { findAgentIDsByStatus } from './agent_status'; -import { savedObjectsClientMock } from '../../../../../../../../src/core/server/mocks'; +import { + elasticsearchServiceMock, + savedObjectsClientMock, +} from '../../../../../../../../src/core/server/mocks'; import { AgentService } from '../../../../../../fleet/server/services'; import { createMockAgentService } from '../../../../../../fleet/server/mocks'; import { Agent } from '../../../../../../fleet/common/types/models'; @@ -14,9 +17,11 @@ import { AgentStatusKueryHelper } from '../../../../../../fleet/common/services' describe('test filtering endpoint hosts by agent status', () => { let mockSavedObjectClient: jest.Mocked; + let mockElasticsearchClient: jest.Mocked; let mockAgentService: jest.Mocked; beforeEach(() => { mockSavedObjectClient = savedObjectsClientMock.create(); + mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockAgentService = createMockAgentService(); }); @@ -30,7 +35,12 @@ describe('test filtering endpoint hosts by agent status', () => { }) ); - const result = await findAgentIDsByStatus(mockAgentService, mockSavedObjectClient, ['online']); + const result = await findAgentIDsByStatus( + mockAgentService, + mockSavedObjectClient, + mockElasticsearchClient, + ['online'] + ); expect(result).toBeDefined(); }); @@ -53,9 +63,14 @@ describe('test filtering endpoint hosts by agent status', () => { }) ); - const result = await findAgentIDsByStatus(mockAgentService, mockSavedObjectClient, ['offline']); + const result = await findAgentIDsByStatus( + mockAgentService, + mockSavedObjectClient, + mockElasticsearchClient, + ['offline'] + ); const offlineKuery = AgentStatusKueryHelper.buildKueryForOfflineAgents(); - expect(mockAgentService.listAgents.mock.calls[0][1].kuery).toEqual( + expect(mockAgentService.listAgents.mock.calls[0][2].kuery).toEqual( expect.stringContaining(offlineKuery) ); expect(result).toBeDefined(); @@ -81,13 +96,15 @@ describe('test filtering endpoint hosts by agent status', () => { }) ); - const result = await findAgentIDsByStatus(mockAgentService, mockSavedObjectClient, [ - 'unenrolling', - 'error', - ]); + const result = await findAgentIDsByStatus( + mockAgentService, + mockSavedObjectClient, + mockElasticsearchClient, + ['unenrolling', 'error'] + ); const unenrollKuery = AgentStatusKueryHelper.buildKueryForUnenrollingAgents(); const errorKuery = AgentStatusKueryHelper.buildKueryForErrorAgents(); - expect(mockAgentService.listAgents.mock.calls[0][1].kuery).toEqual( + expect(mockAgentService.listAgents.mock.calls[0][2].kuery).toEqual( expect.stringContaining(`${unenrollKuery} OR ${errorKuery}`) ); expect(result).toBeDefined(); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts index 395b05c0887e9..4d3fd806dc635 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; import { AgentService } from '../../../../../../fleet/server'; import { AgentStatusKueryHelper } from '../../../../../../fleet/common/services'; import { Agent } from '../../../../../../fleet/common/types/models'; @@ -20,6 +20,7 @@ const STATUS_QUERY_MAP = new Map([ export async function findAgentIDsByStatus( agentService: AgentService, soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, status: string[], pageSize: number = 1000 ): Promise { @@ -39,7 +40,7 @@ export async function findAgentIDsByStatus( let hasMore = true; while (hasMore) { - const agents = await agentService.listAgents(soClient, searchOptions(page++)); + const agents = await agentService.listAgents(soClient, esClient, searchOptions(page++)); result.push(...agents.agents.map((agent: Agent) => agent.id)); hasMore = agents.agents.length > 0; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts index c88f11422d0f0..ea68f6270e730 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts @@ -4,18 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; import { findAllUnenrolledAgentIds } from './unenroll'; -import { savedObjectsClientMock } from '../../../../../../../../src/core/server/mocks'; +import { + elasticsearchServiceMock, + savedObjectsClientMock, +} from '../../../../../../../../src/core/server/mocks'; import { AgentService } from '../../../../../../fleet/server/services'; import { createMockAgentService } from '../../../../../../fleet/server/mocks'; import { Agent } from '../../../../../../fleet/common/types/models'; describe('test find all unenrolled Agent id', () => { let mockSavedObjectClient: jest.Mocked; + let mockElasticsearchClient: jest.Mocked; let mockAgentService: jest.Mocked; beforeEach(() => { mockSavedObjectClient = savedObjectsClientMock.create(); + mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; mockAgentService = createMockAgentService(); }); @@ -53,7 +58,11 @@ describe('test find all unenrolled Agent id', () => { perPage: 1, }) ); - const agentIds = await findAllUnenrolledAgentIds(mockAgentService, mockSavedObjectClient); + const agentIds = await findAllUnenrolledAgentIds( + mockAgentService, + mockSavedObjectClient, + mockElasticsearchClient + ); expect(agentIds).toBeTruthy(); expect(agentIds).toEqual(['id1', 'id2']); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts index 1abea86c1a495..45664f087f1b2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; import { AgentService } from '../../../../../../fleet/server'; import { Agent } from '../../../../../../fleet/common/types/models'; export async function findAllUnenrolledAgentIds( agentService: AgentService, soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, pageSize: number = 1000 ): Promise { const searchOptions = (pageNum: number) => { @@ -29,7 +30,11 @@ export async function findAllUnenrolledAgentIds( let hasMore = true; while (hasMore) { - const unenrolledAgents = await agentService.listAgents(soClient, searchOptions(page++)); + const unenrolledAgents = await agentService.listAgents( + soClient, + esClient, + searchOptions(page++) + ); result.push(...unenrolledAgents.agents.map((agent: Agent) => agent.id)); hasMore = unenrolledAgents.agents.length > 0; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts index 728e3279c52a4..46b67706c99ab 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts @@ -44,6 +44,7 @@ export const getAgentPolicySummaryHandler = function ( const result = await getAgentPolicySummary( endpointAppContext, context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser, request.query.package_name, request.query?.policy_id || undefined ); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts index dd4ade1906bc6..f52535053a531 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts @@ -5,7 +5,11 @@ */ import { SearchResponse } from 'elasticsearch'; -import { ILegacyScopedClusterClient, SavedObjectsClientContract } from 'kibana/server'; +import { + ElasticsearchClient, + ILegacyScopedClusterClient, + SavedObjectsClientContract, +} from 'kibana/server'; import { GetHostPolicyResponse, HostPolicyResponse } from '../../../../common/endpoint/types'; import { INITIAL_POLICY_ID } from './index'; import { Agent } from '../../../../../fleet/common/types/models'; @@ -73,6 +77,7 @@ const transformAgentVersionMap = (versionMap: Map): { [key: stri export async function getAgentPolicySummary( endpointAppContext: EndpointAppContext, soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, packageName: string, policyId?: string, pageSize: number = 1000 @@ -83,6 +88,7 @@ export async function getAgentPolicySummary( await agentVersionsMap( endpointAppContext, soClient, + esClient, `${agentQuery} AND ${AGENT_SAVED_OBJECT_TYPE}.policy_id:${policyId}`, pageSize ) @@ -90,13 +96,14 @@ export async function getAgentPolicySummary( } return transformAgentVersionMap( - await agentVersionsMap(endpointAppContext, soClient, agentQuery, pageSize) + await agentVersionsMap(endpointAppContext, soClient, esClient, agentQuery, pageSize) ); } export async function agentVersionsMap( endpointAppContext: EndpointAppContext, soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, kqlQuery: string, pageSize: number = 1000 ): Promise> { @@ -115,7 +122,7 @@ export async function agentVersionsMap( while (hasMore) { const queryResult = await endpointAppContext.service .getAgentService()! - .listAgents(soClient, searchOptions(page++)); + .listAgents(soClient, esClient, searchOptions(page++)); queryResult.agents.forEach((agent: Agent) => { const agentVersion = agent.local_metadata?.elastic?.agent?.version; if (result.has(agentVersion)) { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index 9cb3f3d20543c..2bd4fcf348f76 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -191,7 +191,7 @@ describe('manifest_manager', () => { expect(packagePolicyService.update.mock.calls.length).toEqual(2); expect( - packagePolicyService.update.mock.calls[0][2].inputs[0].config!.artifact_manifest.value + packagePolicyService.update.mock.calls[0][3].inputs[0].config!.artifact_manifest.value ).toEqual({ manifest_version: '1.0.1', schema_version: 'v1', diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 9f45f39a392f6..44a0fb15e48d9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -293,7 +293,13 @@ export class ManifestManager { }; try { - await this.packagePolicyService.update(this.savedObjectsClient, id, newPackagePolicy); + await this.packagePolicyService.update( + this.savedObjectsClient, + // @ts-ignore + undefined, + id, + newPackagePolicy + ); this.logger.debug( `Updated package policy ${id} with manifest version ${manifest.getSemanticVersion()}` ); diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index d51346ee9645a..020237ad497f7 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -355,6 +355,7 @@ export class Plugin implements IPlugin Date: Wed, 20 Jan 2021 21:15:47 -0600 Subject: [PATCH 33/83] [Workplace Search] Add tests for Custom Source Schema (#88785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add tests for Schema components * Convert components to use clearFlashMessages helper * Remove unused action types These aren’t actually used anymore * Fix type This is actually a string from the server * Move mock to shared mocks * Add tests for logic file * Fix App Search tests Server actually sends back a string for `activeReindexJobId` Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/engine/engine_logic.test.ts | 2 +- .../indexing_status_logic.test.ts | 2 +- .../public/applications/shared/types.ts | 2 +- .../__mocks__/content_sources.mock.ts | 8 + .../components/schema/schema.test.tsx | 125 +++++ .../schema/schema_change_errors.test.tsx | 36 ++ .../schema/schema_fields_table.test.tsx | 41 ++ .../components/schema/schema_fields_table.tsx | 2 +- .../components/schema/schema_logic.test.ts | 470 ++++++++++++++++++ .../components/schema/schema_logic.ts | 12 +- .../views/content_sources/source_logic.ts | 6 +- .../views/content_sources/sources_logic.ts | 4 +- .../views/content_sources/sources_view.tsx | 4 +- 13 files changed, 696 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_fields_table.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts index 62f444cf8f6ab..48cbaeef70c1a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -89,7 +89,7 @@ describe('EngineLogic', () => { const mockReindexJob = { percentageComplete: 50, numDocumentsWithErrors: 2, - activeReindexJobId: 123, + activeReindexJobId: '123', }; EngineLogic.actions.setIndexingStatus(mockReindexJob); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_logic.test.ts index 558271a8fbdc6..0a80f8e361025 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_logic.test.ts @@ -21,7 +21,7 @@ describe('IndexingStatusLogic', () => { const mockStatusResponse = { percentageComplete: 50, numDocumentsWithErrors: 3, - activeReindexJobId: 1, + activeReindexJobId: '1', }; beforeEach(() => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index f5833a0ac9f8e..0ad5f292ebed7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -33,7 +33,7 @@ export interface SchemaConflicts { export interface IIndexingStatus { percentageComplete: number; numDocumentsWithErrors: number; - activeReindexJobId: number; + activeReindexJobId: string; } export interface IndexJob extends IIndexingStatus { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts index b3962a7c88cc8..3cd84d90d9a86 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts @@ -258,3 +258,11 @@ export const exampleResult = { }, ], }; + +export const mostRecentIndexJob = { + isActive: true, + hasErrors: true, + percentageComplete: 50, + activeReindexJobId: '123', + numDocumentsWithErrors: 1, +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema.test.tsx new file mode 100644 index 0000000000000..1d7a73970bf1c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema.test.tsx @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiEmptyPrompt, EuiFieldSearch } from '@elastic/eui'; + +import { mostRecentIndexJob } from '../../../../__mocks__/content_sources.mock'; + +import { IndexingStatus } from '../../../../../shared/indexing_status'; +import { Loading } from '../../../../../shared/loading'; +import { SchemaAddFieldModal } from '../../../../../shared/schema/schema_add_field_modal'; + +import { SchemaFieldsTable } from './schema_fields_table'; + +import { Schema } from './schema'; + +describe('Schema', () => { + const initializeSchema = jest.fn(); + const onIndexingComplete = jest.fn(); + const addNewField = jest.fn(); + const updateFields = jest.fn(); + const openAddFieldModal = jest.fn(); + const closeAddFieldModal = jest.fn(); + const setFilterValue = jest.fn(); + + const sourceId = '123'; + const activeSchema = { + foo: 'string', + }; + const filterValue = ''; + const showAddFieldModal = false; + const addFieldFormErrors = null; + const formUnchanged = true; + const dataLoading = false; + const isOrganization = true; + + const mockValues = { + sourceId, + activeSchema, + filterValue, + showAddFieldModal, + addFieldFormErrors, + mostRecentIndexJob: {}, + formUnchanged, + dataLoading, + isOrganization, + }; + + beforeEach(() => { + setMockValues({ ...mockValues }); + setMockActions({ + initializeSchema, + onIndexingComplete, + addNewField, + updateFields, + openAddFieldModal, + closeAddFieldModal, + setFilterValue, + }); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(SchemaFieldsTable)).toHaveLength(1); + }); + + it('returns loading when loading', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('handles empty state', () => { + setMockValues({ ...mockValues, activeSchema: {} }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); + }); + + it('renders modal', () => { + setMockValues({ ...mockValues, showAddFieldModal: true }); + const wrapper = shallow(); + + expect(wrapper.find(SchemaAddFieldModal)).toHaveLength(1); + }); + + it('gets search results when filters changed', () => { + const wrapper = shallow(); + const input = wrapper.find(EuiFieldSearch); + input.simulate('change', { target: { value: 'Query' } }); + + expect(setFilterValue).toHaveBeenCalledWith('Query'); + }); + + it('renders IndexingStatus (org)', () => { + setMockValues({ ...mockValues, mostRecentIndexJob }); + const wrapper = shallow(); + + expect(wrapper.find(IndexingStatus)).toHaveLength(1); + expect(wrapper.find(IndexingStatus).prop('statusPath')).toEqual( + '/api/workplace_search/org/sources/123/reindex_job/123/status' + ); + }); + + it('renders IndexingStatus (account)', () => { + setMockValues({ ...mockValues, mostRecentIndexJob, isOrganization: false }); + const wrapper = shallow(); + + expect(wrapper.find(IndexingStatus)).toHaveLength(1); + expect(wrapper.find(IndexingStatus).prop('statusPath')).toEqual( + '/api/workplace_search/account/sources/123/reindex_job/123/status' + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.test.tsx new file mode 100644 index 0000000000000..35a086369f390 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.test.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; +import { useParams } from 'react-router-dom'; + +import { SchemaErrorsAccordion } from '../../../../../shared/schema/schema_errors_accordion'; + +import { SchemaChangeErrors } from './schema_change_errors'; + +describe('SchemaChangeErrors', () => { + const fieldCoercionErrors = [] as any; + const serverSchema = { + foo: 'string', + }; + it('renders', () => { + setMockValues({ fieldCoercionErrors, serverSchema }); + setMockActions({ initializeSchemaFieldErrors: jest.fn() }); + + (useParams as jest.Mock).mockImplementationOnce(() => ({ + activeReindexJobId: '1', + sourceId: '123', + })); + const wrapper = shallow(); + + expect(wrapper.find(SchemaErrorsAccordion)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_fields_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_fields_table.test.tsx new file mode 100644 index 0000000000000..cf16a04d92ecf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_fields_table.test.tsx @@ -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 '../../../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SchemaExistingField } from '../../../../../shared/schema/schema_existing_field'; + +import { SchemaFieldsTable } from './schema_fields_table'; + +describe('SchemaFieldsTable', () => { + const filterValue = ''; + const filteredSchemaFields = { + foo: 'string', + }; + + beforeEach(() => { + setMockActions({ updateExistingFieldType: jest.fn() }); + }); + + it('renders', () => { + setMockValues({ filterValue, filteredSchemaFields }); + const wrapper = shallow(); + + expect(wrapper.find(SchemaExistingField)).toHaveLength(1); + }); + + it('handles no results', () => { + setMockValues({ filterValue, filteredSchemaFields: {} }); + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="NoResultsMessage"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_fields_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_fields_table.tsx index 8f697b2b5c35d..1670e2128c0af 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_fields_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_fields_table.tsx @@ -66,7 +66,7 @@ export const SchemaFieldsTable: React.FC = () => { ) : ( -

+

{i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.schema.filter.noResults.message', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts new file mode 100644 index 0000000000000..2c3aa6114c7da --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts @@ -0,0 +1,470 @@ +/* + * 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 { + LogicMounter, + mockFlashMessageHelpers, + mockHttpValues, + expectedAsyncError, +} from '../../../../../__mocks__'; + +const contentSource = { id: 'source123' }; +jest.mock('../../source_logic', () => ({ + SourceLogic: { values: { contentSource } }, +})); + +import { AppLogic } from '../../../../app_logic'; +jest.mock('../../../../app_logic', () => ({ + AppLogic: { values: { isOrganization: true } }, +})); + +const spyScrollTo = jest.fn(); +Object.defineProperty(global.window, 'scrollTo', { value: spyScrollTo }); + +import { mostRecentIndexJob } from '../../../../__mocks__/content_sources.mock'; +import { TEXT } from '../../../../../shared/constants/field_types'; +import { ADD, UPDATE } from '../../../../../shared/constants/operations'; + +import { + SCHEMA_FIELD_ERRORS_ERROR_MESSAGE, + SCHEMA_FIELD_ADDED_MESSAGE, + SCHEMA_UPDATED_MESSAGE, +} from './constants'; + +import { SchemaLogic, dataTypeOptions } from './schema_logic'; + +describe('SchemaLogic', () => { + const { http } = mockHttpValues; + const { clearFlashMessages, flashAPIErrors, setSuccessMessage } = mockFlashMessageHelpers; + const { mount } = new LogicMounter(SchemaLogic); + + const defaultValues = { + sourceId: '', + activeSchema: {}, + serverSchema: {}, + filterValue: '', + filteredSchemaFields: {}, + dataTypeOptions, + showAddFieldModal: false, + addFieldFormErrors: null, + mostRecentIndexJob: {}, + fieldCoercionErrors: {}, + newFieldType: TEXT, + rawFieldName: '', + formUnchanged: true, + dataLoading: true, + }; + + const schema = { + foo: 'text', + } as any; + + const fieldCoercionErrors = [ + { + external_id: '123', + error: 'error', + }, + ] as any; + + const errors = ['this is an error']; + + const serverResponse = { + schema, + sourceId: contentSource.id, + mostRecentIndexJob, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mount(); + }); + + it('has expected default values', () => { + expect(SchemaLogic.values).toEqual(defaultValues); + }); + + describe('actions', () => { + it('onInitializeSchema', () => { + SchemaLogic.actions.onInitializeSchema(serverResponse); + + expect(SchemaLogic.values.sourceId).toEqual(contentSource.id); + expect(SchemaLogic.values.activeSchema).toEqual(schema); + expect(SchemaLogic.values.serverSchema).toEqual(schema); + expect(SchemaLogic.values.mostRecentIndexJob).toEqual(mostRecentIndexJob); + expect(SchemaLogic.values.dataLoading).toEqual(false); + }); + + it('onInitializeSchemaFieldErrors', () => { + SchemaLogic.actions.onInitializeSchemaFieldErrors({ fieldCoercionErrors }); + + expect(SchemaLogic.values.fieldCoercionErrors).toEqual(fieldCoercionErrors); + }); + it('onSchemaSetSuccess', () => { + SchemaLogic.actions.onSchemaSetSuccess({ + schema, + mostRecentIndexJob, + }); + + expect(SchemaLogic.values.activeSchema).toEqual(schema); + expect(SchemaLogic.values.serverSchema).toEqual(schema); + expect(SchemaLogic.values.mostRecentIndexJob).toEqual(mostRecentIndexJob); + expect(SchemaLogic.values.newFieldType).toEqual(TEXT); + expect(SchemaLogic.values.addFieldFormErrors).toEqual(null); + expect(SchemaLogic.values.formUnchanged).toEqual(true); + expect(SchemaLogic.values.showAddFieldModal).toEqual(false); + expect(SchemaLogic.values.dataLoading).toEqual(false); + expect(SchemaLogic.values.rawFieldName).toEqual(''); + }); + + it('onSchemaSetFormErrors', () => { + SchemaLogic.actions.onSchemaSetFormErrors(errors); + + expect(SchemaLogic.values.addFieldFormErrors).toEqual(errors); + }); + + it('updateNewFieldType', () => { + const NUMBER = 'number'; + SchemaLogic.actions.updateNewFieldType(NUMBER); + + expect(SchemaLogic.values.newFieldType).toEqual(NUMBER); + }); + + it('onFieldUpdate', () => { + SchemaLogic.actions.onFieldUpdate({ schema, formUnchanged: false }); + + expect(SchemaLogic.values.activeSchema).toEqual(schema); + expect(SchemaLogic.values.formUnchanged).toEqual(false); + }); + + it('onIndexingComplete', () => { + SchemaLogic.actions.onIndexingComplete(1); + + expect(SchemaLogic.values.mostRecentIndexJob).toEqual({ + ...mostRecentIndexJob, + activeReindexJobId: undefined, + percentageComplete: 100, + hasErrors: true, + isActive: false, + }); + }); + + it('resetMostRecentIndexJob', () => { + SchemaLogic.actions.resetMostRecentIndexJob(mostRecentIndexJob); + + expect(SchemaLogic.values.mostRecentIndexJob).toEqual(mostRecentIndexJob); + }); + + it('setFieldName', () => { + const NAME = 'name'; + SchemaLogic.actions.setFieldName(NAME); + + expect(SchemaLogic.values.rawFieldName).toEqual(NAME); + }); + + it('setFilterValue', () => { + const VALUE = 'string'; + SchemaLogic.actions.setFilterValue(VALUE); + + expect(SchemaLogic.values.filterValue).toEqual(VALUE); + }); + + it('openAddFieldModal', () => { + SchemaLogic.actions.openAddFieldModal(); + + expect(SchemaLogic.values.showAddFieldModal).toEqual(true); + }); + + it('closeAddFieldModal', () => { + SchemaLogic.actions.onSchemaSetFormErrors(errors); + SchemaLogic.actions.openAddFieldModal(); + SchemaLogic.actions.closeAddFieldModal(); + + expect(SchemaLogic.values.showAddFieldModal).toEqual(false); + expect(SchemaLogic.values.addFieldFormErrors).toEqual(null); + }); + + it('resetSchemaState', () => { + SchemaLogic.actions.resetSchemaState(); + + expect(SchemaLogic.values.dataLoading).toEqual(true); + expect(clearFlashMessages).toHaveBeenCalled(); + }); + }); + + describe('listeners', () => { + describe('initializeSchema', () => { + it('calls API and sets values (org)', async () => { + const onInitializeSchemaSpy = jest.spyOn(SchemaLogic.actions, 'onInitializeSchema'); + const promise = Promise.resolve(serverResponse); + http.get.mockReturnValue(promise); + SchemaLogic.actions.initializeSchema(); + + expect(http.get).toHaveBeenCalledWith( + '/api/workplace_search/org/sources/source123/schemas' + ); + await promise; + expect(onInitializeSchemaSpy).toHaveBeenCalledWith(serverResponse); + }); + + it('calls API and sets values (account)', async () => { + AppLogic.values.isOrganization = false; + + const onInitializeSchemaSpy = jest.spyOn(SchemaLogic.actions, 'onInitializeSchema'); + const promise = Promise.resolve(serverResponse); + http.get.mockReturnValue(promise); + SchemaLogic.actions.initializeSchema(); + + expect(http.get).toHaveBeenCalledWith( + '/api/workplace_search/account/sources/source123/schemas' + ); + await promise; + expect(onInitializeSchemaSpy).toHaveBeenCalledWith(serverResponse); + }); + + it('handles error', async () => { + const promise = Promise.reject('this is an error'); + http.get.mockReturnValue(promise); + SchemaLogic.actions.initializeSchema(); + await expectedAsyncError(promise); + + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + }); + }); + + describe('initializeSchemaFieldErrors', () => { + it('calls API and sets values (org)', async () => { + AppLogic.values.isOrganization = true; + const onInitializeSchemaFieldErrorsSpy = jest.spyOn( + SchemaLogic.actions, + 'onInitializeSchemaFieldErrors' + ); + const initPromise = Promise.resolve(serverResponse); + const promise = Promise.resolve({ fieldCoercionErrors }); + http.get.mockReturnValue(initPromise); + http.get.mockReturnValue(promise); + SchemaLogic.actions.initializeSchemaFieldErrors( + mostRecentIndexJob.activeReindexJobId, + contentSource.id + ); + + expect(http.get).toHaveBeenCalledWith( + '/api/workplace_search/org/sources/source123/schemas' + ); + + await initPromise; + expect(http.get).toHaveBeenCalledWith( + '/api/workplace_search/org/sources/source123/reindex_job/123' + ); + + await promise; + expect(onInitializeSchemaFieldErrorsSpy).toHaveBeenCalledWith({ + fieldCoercionErrors, + }); + }); + + it('calls API and sets values (account)', async () => { + AppLogic.values.isOrganization = false; + + const onInitializeSchemaFieldErrorsSpy = jest.spyOn( + SchemaLogic.actions, + 'onInitializeSchemaFieldErrors' + ); + const initPromise = Promise.resolve(serverResponse); + const promise = Promise.resolve({ fieldCoercionErrors }); + http.get.mockReturnValue(initPromise); + http.get.mockReturnValue(promise); + SchemaLogic.actions.initializeSchemaFieldErrors( + mostRecentIndexJob.activeReindexJobId, + contentSource.id + ); + + expect(http.get).toHaveBeenCalledWith( + '/api/workplace_search/account/sources/source123/schemas' + ); + + await initPromise; + expect(http.get).toHaveBeenCalledWith( + '/api/workplace_search/account/sources/source123/reindex_job/123' + ); + + await promise; + expect(onInitializeSchemaFieldErrorsSpy).toHaveBeenCalledWith({ + fieldCoercionErrors, + }); + }); + + it('handles error', async () => { + const promise = Promise.reject({ error: 'this is an error' }); + http.get.mockReturnValue(promise); + SchemaLogic.actions.initializeSchemaFieldErrors( + mostRecentIndexJob.activeReindexJobId, + contentSource.id + ); + await expectedAsyncError(promise); + + expect(flashAPIErrors).toHaveBeenCalledWith({ + error: 'this is an error', + message: SCHEMA_FIELD_ERRORS_ERROR_MESSAGE, + }); + }); + }); + + it('addNewField', () => { + const setServerFieldSpy = jest.spyOn(SchemaLogic.actions, 'setServerField'); + SchemaLogic.actions.onInitializeSchema(serverResponse); + const newSchema = { + ...schema, + bar: 'number', + }; + SchemaLogic.actions.addNewField('bar', 'number'); + + expect(setServerFieldSpy).toHaveBeenCalledWith(newSchema, ADD); + }); + + it('updateExistingFieldType', () => { + const onFieldUpdateSpy = jest.spyOn(SchemaLogic.actions, 'onFieldUpdate'); + SchemaLogic.actions.onInitializeSchema(serverResponse); + const newSchema = { + foo: 'number', + }; + SchemaLogic.actions.updateExistingFieldType('foo', 'number'); + + expect(onFieldUpdateSpy).toHaveBeenCalledWith({ schema: newSchema, formUnchanged: false }); + }); + + it('updateFields', () => { + const setServerFieldSpy = jest.spyOn(SchemaLogic.actions, 'setServerField'); + SchemaLogic.actions.onInitializeSchema(serverResponse); + SchemaLogic.actions.updateFields(); + + expect(setServerFieldSpy).toHaveBeenCalledWith(schema, UPDATE); + }); + + describe('setServerField', () => { + beforeEach(() => { + SchemaLogic.actions.onInitializeSchema(serverResponse); + }); + + describe('adding a field', () => { + it('calls API and sets values (org)', async () => { + AppLogic.values.isOrganization = true; + const onSchemaSetSuccessSpy = jest.spyOn(SchemaLogic.actions, 'onSchemaSetSuccess'); + const promise = Promise.resolve(serverResponse); + http.post.mockReturnValue(promise); + SchemaLogic.actions.setServerField(schema, ADD); + + expect(http.post).toHaveBeenCalledWith( + '/api/workplace_search/org/sources/source123/schemas', + { + body: JSON.stringify({ ...schema }), + } + ); + await promise; + expect(setSuccessMessage).toHaveBeenCalledWith(SCHEMA_FIELD_ADDED_MESSAGE); + expect(onSchemaSetSuccessSpy).toHaveBeenCalledWith(serverResponse); + }); + + it('calls API and sets values (account)', async () => { + AppLogic.values.isOrganization = false; + + const onSchemaSetSuccessSpy = jest.spyOn(SchemaLogic.actions, 'onSchemaSetSuccess'); + const promise = Promise.resolve(serverResponse); + http.post.mockReturnValue(promise); + SchemaLogic.actions.setServerField(schema, ADD); + + expect(http.post).toHaveBeenCalledWith( + '/api/workplace_search/account/sources/source123/schemas', + { + body: JSON.stringify({ ...schema }), + } + ); + await promise; + expect(onSchemaSetSuccessSpy).toHaveBeenCalledWith(serverResponse); + }); + + it('handles error', async () => { + const onSchemaSetFormErrorsSpy = jest.spyOn(SchemaLogic.actions, 'onSchemaSetFormErrors'); + const promise = Promise.reject({ message: 'this is an error' }); + http.post.mockReturnValue(promise); + SchemaLogic.actions.setServerField(schema, ADD); + await expectedAsyncError(promise); + + expect(onSchemaSetFormErrorsSpy).toHaveBeenCalledWith('this is an error'); + }); + }); + + describe('updating a field', () => { + it('calls API and sets values (org)', async () => { + AppLogic.values.isOrganization = true; + const onSchemaSetSuccessSpy = jest.spyOn(SchemaLogic.actions, 'onSchemaSetSuccess'); + const promise = Promise.resolve(serverResponse); + http.post.mockReturnValue(promise); + SchemaLogic.actions.setServerField(schema, UPDATE); + + expect(http.post).toHaveBeenCalledWith( + '/api/workplace_search/org/sources/source123/schemas', + { + body: JSON.stringify({ ...schema }), + } + ); + await promise; + expect(setSuccessMessage).toHaveBeenCalledWith(SCHEMA_UPDATED_MESSAGE); + expect(onSchemaSetSuccessSpy).toHaveBeenCalledWith(serverResponse); + }); + + it('calls API and sets values (account)', async () => { + AppLogic.values.isOrganization = false; + + const onSchemaSetSuccessSpy = jest.spyOn(SchemaLogic.actions, 'onSchemaSetSuccess'); + const promise = Promise.resolve(serverResponse); + http.post.mockReturnValue(promise); + SchemaLogic.actions.setServerField(schema, UPDATE); + + expect(http.post).toHaveBeenCalledWith( + '/api/workplace_search/account/sources/source123/schemas', + { + body: JSON.stringify({ ...schema }), + } + ); + await promise; + expect(onSchemaSetSuccessSpy).toHaveBeenCalledWith(serverResponse); + }); + + it('handles error', async () => { + const promise = Promise.reject('this is an error'); + http.post.mockReturnValue(promise); + SchemaLogic.actions.setServerField(schema, UPDATE); + await expectedAsyncError(promise); + + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + }); + }); + }); + }); + + describe('selectors', () => { + describe('filteredSchemaFields', () => { + it('handles empty response', () => { + SchemaLogic.actions.onInitializeSchema(serverResponse); + SchemaLogic.actions.setFilterValue('baz'); + + expect(SchemaLogic.values.filteredSchemaFields).toEqual({}); + }); + + it('handles filtered response', () => { + const newSchema = { + ...schema, + bar: 'number', + }; + SchemaLogic.actions.onInitializeSchema(serverResponse); + SchemaLogic.actions.onFieldUpdate({ schema: newSchema, formUnchanged: false }); + SchemaLogic.actions.setFilterValue('foo'); + + expect(SchemaLogic.values.filteredSchemaFields).toEqual(schema); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts index 36eb3fc67b2c2..9a9435a07e245 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts @@ -17,7 +17,7 @@ import { OptionValue } from '../../../../types'; import { flashAPIErrors, setSuccessMessage, - FlashMessagesLogic, + clearFlashMessages, } from '../../../../../shared/flash_messages'; import { AppLogic } from '../../../../app_logic'; @@ -46,7 +46,6 @@ interface SchemaActions { }): { schema: Schema; formUnchanged: boolean }; onIndexingComplete(numDocumentsWithErrors: number): number; resetMostRecentIndexJob(emptyReindexJob: IndexJob): IndexJob; - showFieldSuccess(successMessage: string): string; setFieldName(rawFieldName: string): string; setFilterValue(filterValue: string): string; addNewField( @@ -111,7 +110,7 @@ interface SchemaChangeErrorsProps { fieldCoercionErrors: FieldCoercionErrors; } -const dataTypeOptions = [ +export const dataTypeOptions = [ { value: 'text', text: 'Text' }, { value: 'date', text: 'Date' }, { value: 'number', text: 'Number' }, @@ -132,7 +131,6 @@ export const SchemaLogic = kea>({ }), onIndexingComplete: (numDocumentsWithErrors: number) => numDocumentsWithErrors, resetMostRecentIndexJob: (emptyReindexJob: IndexJob) => emptyReindexJob, - showFieldSuccess: (successMessage: string) => successMessage, setFieldName: (rawFieldName: string) => rawFieldName, setFilterValue: (filterValue: string) => filterValue, openAddFieldModal: () => true, @@ -326,7 +324,7 @@ export const SchemaLogic = kea>({ const emptyReindexJob = { percentageComplete: 100, numDocumentsWithErrors: 0, - activeReindexJobId: 0, + activeReindexJobId: '', isActive: false, }; @@ -348,10 +346,10 @@ export const SchemaLogic = kea>({ } }, resetMostRecentIndexJob: () => { - FlashMessagesLogic.actions.clearFlashMessages(); + clearFlashMessages(); }, resetSchemaState: () => { - FlashMessagesLogic.actions.clearFlashMessages(); + clearFlashMessages(); }, }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index 0891687d425f5..9a68d2234e3ad 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -15,7 +15,7 @@ import { flashAPIErrors, setSuccessMessage, setQueuedSuccessMessage, - FlashMessagesLogic, + clearFlashMessages, } from '../../../shared/flash_messages'; import { DEFAULT_META } from '../../../shared/constants'; @@ -246,7 +246,7 @@ export const SourceLogic = kea>({ } }, removeContentSource: async ({ sourceId, successCallback }) => { - FlashMessagesLogic.actions.clearFlashMessages(); + clearFlashMessages(); const { isOrganization } = AppLogic.values; const route = isOrganization ? `/api/workplace_search/org/sources/${sourceId}` @@ -292,7 +292,7 @@ export const SourceLogic = kea>({ ); }, resetSourceState: () => { - FlashMessagesLogic.actions.clearFlashMessages(); + clearFlashMessages(); }, }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts index cb8df1d312198..ab71f76484561 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts @@ -15,7 +15,7 @@ import { HttpLogic } from '../../../shared/http'; import { flashAPIErrors, setQueuedSuccessMessage, - FlashMessagesLogic, + clearFlashMessages, } from '../../../shared/flash_messages'; import { Connector, ContentSourceDetails, ContentSourceStatus, SourceDataItem } from '../../types'; @@ -233,7 +233,7 @@ export const SourcesLogic = kea>( ); }, resetFlashMessages: () => { - FlashMessagesLogic.actions.clearFlashMessages(); + clearFlashMessages(); }, }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx index 7485f986076d7..7e3c14b203e9e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx @@ -22,7 +22,7 @@ import { EuiText, } from '@elastic/eui'; -import { FlashMessagesLogic } from '../../../shared/flash_messages'; +import { clearFlashMessages } from '../../../shared/flash_messages'; import { Loading } from '../../../shared/loading'; import { SourceIcon } from '../../components/shared/source_icon'; @@ -49,7 +49,7 @@ export const SourcesView: React.FC = ({ children }) => { const pollingInterval = window.setInterval(pollForSourceStatusChanges, POLLING_INTERVAL); return () => { - FlashMessagesLogic.actions.clearFlashMessages(); + clearFlashMessages(); clearInterval(pollingInterval); }; }, []); From 97c7f5c8a1744cee31f679d71f4809ba131ff259 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 20 Jan 2021 20:51:18 -0700 Subject: [PATCH 34/83] skip flaky suite (#88926) (#88927) (#88929) --- x-pack/test/accessibility/apps/lens.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/test/accessibility/apps/lens.ts b/x-pack/test/accessibility/apps/lens.ts index bfd79f070d284..a7cacd0ad1cbb 100644 --- a/x-pack/test/accessibility/apps/lens.ts +++ b/x-pack/test/accessibility/apps/lens.ts @@ -12,7 +12,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const listingTable = getService('listingTable'); - describe('Lens', () => { + // FLAKY: https://github.com/elastic/kibana/issues/88926 + // FLAKY: https://github.com/elastic/kibana/issues/88927 + // FLAKY: https://github.com/elastic/kibana/issues/88929 + describe.skip('Lens', () => { const lensChartName = 'MyLensChart'; before(async () => { await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { From d28fa36e8a3047d354723edb3f8e4d7d55272811 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 20 Jan 2021 21:01:57 -0700 Subject: [PATCH 35/83] skip flaky suite (#88928) --- .../search/sessions_mgmt/components/table/table.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index 357f17649394b..51cec8f2afeff 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -127,7 +127,8 @@ describe('Background Search Session Management Table', () => { }); }); - describe('fetching sessions data', () => { + // FLAKY: https://github.com/elastic/kibana/issues/88928 + describe.skip('fetching sessions data', () => { test('re-fetches data', async () => { jest.useFakeTimers(); sessionsClient.find = jest.fn(); From c7267b63dfbe38119bf9252e20ee70647b02f40c Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Thu, 21 Jan 2021 07:59:00 +0100 Subject: [PATCH 36/83] Reject authentication requests if license is not available. (#88850) --- .../security/common/licensing/index.mock.ts | 2 +- .../authentication_service.test.ts | 17 +++++++++++++++++ .../authentication/authentication_service.ts | 14 +++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security/common/licensing/index.mock.ts b/x-pack/plugins/security/common/licensing/index.mock.ts index 88cb3206a253c..ed89ad5cc5052 100644 --- a/x-pack/plugins/security/common/licensing/index.mock.ts +++ b/x-pack/plugins/security/common/licensing/index.mock.ts @@ -9,7 +9,7 @@ import { SecurityLicense, SecurityLicenseFeatures } from '.'; export const licenseMock = { create: (features?: Partial): jest.Mocked => ({ - isLicenseAvailable: jest.fn(), + isLicenseAvailable: jest.fn().mockReturnValue(true), isEnabled: jest.fn().mockReturnValue(true), getType: jest.fn().mockReturnValue('basic'), getFeatures: jest.fn(), diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index 244cf1d0a8f51..59771c5027012 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -121,6 +121,23 @@ describe('AuthenticationService', () => { .authenticate; }); + it('returns error if license is not available.', async () => { + const mockResponse = httpServerMock.createLifecycleResponseFactory(); + + mockSetupAuthenticationParams.license.isLicenseAvailable.mockReturnValue(false); + + await authHandler(httpServerMock.createKibanaRequest(), mockResponse, mockAuthToolkit); + + expect(mockResponse.customError).toHaveBeenCalledTimes(1); + expect(mockResponse.customError).toHaveBeenCalledWith({ + body: 'License is not available.', + statusCode: 503, + headers: { 'Retry-After': '30' }, + }); + expect(mockAuthToolkit.authenticated).not.toHaveBeenCalled(); + expect(mockAuthToolkit.redirected).not.toHaveBeenCalled(); + }); + it('replies with no credentials when security is disabled in elasticsearch', async () => { const mockRequest = httpServerMock.createKibanaRequest(); const mockResponse = httpServerMock.createLifecycleResponseFactory(); diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index 6cd20592f21a4..e435ae43f3bf3 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -106,8 +106,20 @@ export class AuthenticationService { }); http.registerAuth(async (request, response, t) => { - // If security is disabled continue with no user credentials and delete the client cookie as well. + if (!license.isLicenseAvailable()) { + this.logger.error('License is not available, authentication is not possible.'); + return response.customError({ + body: 'License is not available.', + statusCode: 503, + headers: { 'Retry-After': '30' }, + }); + } + + // If security is disabled, then continue with no user credentials. if (!license.isEnabled()) { + this.logger.debug( + 'Current license does not support any security features, authentication is not needed.' + ); return t.authenticated(); } From 922abfa21e1dac9a5f5dea837eb4f3a25b4999b9 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 21 Jan 2021 08:11:59 +0100 Subject: [PATCH 37/83] [ML] Transforms: Fixes available fields for sort options for latest configuration (#88617) - Fixes the transform preview header to display the heading text in any case and the copy-to-clipboard button for latest configurations (the copy-to-clipboard option for pivot is displayed within the form). - Fix to avoid listing all fields for the sort option for latest configuration and only show date fields - Fixes ambiguous form field labels --- .../components/data_grid/data_grid.tsx | 30 ++++---- .../hooks/use_latest_function_config.test.ts | 45 +++++++++++ .../hooks/use_latest_function_config.ts | 16 ++-- .../step_define/latest_function_form.tsx | 76 ++++++++++++++----- .../step_define/step_define_form.tsx | 38 +++++++--- .../step_define/step_define_summary.tsx | 3 +- .../step_details/step_details_form.tsx | 4 +- .../step_details/step_details_time_field.tsx | 4 +- 8 files changed, 158 insertions(+), 58 deletions(-) create mode 100644 x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.test.ts 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 c499d1153a238..4eaf33c6f1570 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 @@ -260,20 +260,22 @@ export const DataGrid: FC = memo( - - - {(copy: () => void) => ( - - )} - - + {props.copyToClipboard && props.copyToClipboardDescription && ( + + + {(copy: () => void) => ( + + )} + + + )} )} {errorCallout !== undefined && ( diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.test.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.test.ts new file mode 100644 index 0000000000000..593e37175a2e5 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { LatestFunctionConfigUI } from '../../../../../../../common/types/transform'; + +import { latestConfigMapper, validateLatestConfig } from './use_latest_function_config'; + +describe('useLatestFunctionConfig', () => { + it('should return a valid configuration', () => { + const config: LatestFunctionConfigUI = { + unique_key: [{ label: 'the-unique-key-label', value: 'the-unique-key' }], + sort: { label: 'the-sort-label', value: 'the-sort' }, + }; + + const apiConfig = latestConfigMapper.toAPIConfig(config); + + expect(apiConfig).toEqual({ + unique_key: ['the-unique-key'], + sort: 'the-sort', + }); + expect(validateLatestConfig(apiConfig).isValid).toBe(true); + }); + + it('should return an invalid partial configuration', () => { + const config: LatestFunctionConfigUI = { + unique_key: [{ label: 'the-unique-key-label', value: 'the-unique-key' }], + sort: { label: 'the-sort-label', value: undefined }, + }; + + const apiConfig = latestConfigMapper.toAPIConfig(config); + + expect(apiConfig).toEqual({ + unique_key: ['the-unique-key'], + sort: '', + }); + expect(validateLatestConfig(apiConfig).isValid).toBe(false); + }); + + it('should return false for isValid if no configuration given', () => { + expect(validateLatestConfig().isValid).toBe(false); + }); +}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts index 7df6b11dc27ec..5c2d0cd1b1042 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts @@ -18,14 +18,10 @@ import { useAppDependencies } from '../../../../../app_dependencies'; * Latest function config mapper between API and UI */ export const latestConfigMapper = { - toAPIConfig(uiConfig: LatestFunctionConfigUI): LatestFunctionConfig | undefined { - if (uiConfig.sort === undefined || !uiConfig.unique_key?.length) { - return; - } - + toAPIConfig(uiConfig: LatestFunctionConfigUI): LatestFunctionConfig { return { - unique_key: uiConfig.unique_key.map((v) => v.value!), - sort: uiConfig.sort.value!, + unique_key: uiConfig.unique_key?.length ? uiConfig.unique_key.map((v) => v.value!) : [], + sort: uiConfig.sort?.value !== undefined ? uiConfig.sort.value! : '', }; }, toUIConfig() {}, @@ -56,7 +52,8 @@ function getOptions( })); const sortFieldOptions: Array> = indexPattern.fields - .filter((v) => !ignoreFieldNames.has(v.name) && v.sortable) + // The backend API for `latest` allows all field types for sort but the UI will be limited to `date`. + .filter((v) => !ignoreFieldNames.has(v.name) && v.sortable && v.type === 'date') .map((v) => ({ label: v.displayName, value: v.name, @@ -69,7 +66,8 @@ function getOptions( * Validates latest function configuration */ export function validateLatestConfig(config?: LatestFunctionConfig) { - const isValid: boolean = !!config?.unique_key?.length && config?.sort !== undefined; + const isValid: boolean = + !!config?.unique_key?.length && typeof config?.sort === 'string' && config?.sort.length > 0; return { isValid, ...(isValid diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx index b4d035940192d..0a64b6803f19c 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx @@ -7,14 +7,20 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiComboBox, EuiFormRow } from '@elastic/eui'; +import { EuiButtonIcon, EuiCallOut, EuiComboBox, EuiCopy, EuiFormRow } from '@elastic/eui'; import { LatestFunctionService } from './hooks/use_latest_function_config'; interface LatestFunctionFormProps { + copyToClipboard: string; + copyToClipboardDescription: string; latestFunctionService: LatestFunctionService; } -export const LatestFunctionForm: FC = ({ latestFunctionService }) => { +export const LatestFunctionForm: FC = ({ + copyToClipboard, + copyToClipboardDescription, + latestFunctionService, +}) => { return ( <> = ({ latestFunction defaultMessage="Sort field" /> } + helpText={ + latestFunctionService.sortFieldOptions.length > 0 + ? i18n.translate('xpack.transform.stepDefineForm.sortHelpText', { + defaultMessage: 'Select the date field to be used to identify the latest document.', + }) + : undefined + } > - { - latestFunctionService.updateLatestFunctionConfig({ - sort: { value: selected[0].value, label: selected[0].label as string }, - }); - }} - isClearable={false} - data-test-subj="transformWizardSortFieldSelector" - /> + <> + {latestFunctionService.sortFieldOptions.length > 0 && ( + { + latestFunctionService.updateLatestFunctionConfig({ + sort: { value: selected[0].value, label: selected[0].label as string }, + }); + }} + isClearable={false} + data-test-subj="transformWizardSortFieldSelector" + /> + )} + {latestFunctionService.sortFieldOptions.length === 0 && ( + +

+ {' '} + + {(copy: () => void) => ( + + )} + +

+ + )} + ); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index d5c83357f4db5..743572632b5af 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -104,12 +104,6 @@ export const StepDefineForm: FC = React.memo((props) => { : stepDefineForm.latestFunctionConfig.requestPayload ); - const pivotPreviewProps = { - ...usePivotData(indexPattern.title, pivotQuery, validationStatus, requestPayload), - dataTestSubj: 'transformPivotPreview', - toastNotifications, - }; - const copyToClipboardSource = getIndexDevConsoleStatement(pivotQuery, indexPattern.title); const copyToClipboardSourceDescription = i18n.translate( 'xpack.transform.indexPreview.copyClipboardTooltip', @@ -122,10 +116,25 @@ export const StepDefineForm: FC = React.memo((props) => { const copyToClipboardPivotDescription = i18n.translate( 'xpack.transform.pivotPreview.copyClipboardTooltip', { - defaultMessage: 'Copy Dev Console statement of the pivot preview to the clipboard.', + defaultMessage: 'Copy Dev Console statement of the transform preview to the clipboard.', } ); + const pivotPreviewProps = { + ...usePivotData(indexPattern.title, pivotQuery, validationStatus, requestPayload), + dataTestSubj: 'transformPivotPreview', + title: i18n.translate('xpack.transform.pivotPreview.transformPreviewTitle', { + defaultMessage: 'Transform preview', + }), + toastNotifications, + ...(stepDefineForm.transformFunction === TRANSFORM_FUNCTION.LATEST + ? { + copyToClipboard: copyToClipboardPivot, + copyToClipboardDescription: copyToClipboardPivotDescription, + } + : {}), + }; + const applySourceChangesHandler = () => { const sourceConfig = JSON.parse(advancedEditorSourceConfig); stepDefineForm.searchBar.actions.setSearchQuery(sourceConfig); @@ -377,12 +386,21 @@ export const StepDefineForm: FC = React.memo((props) => { ) : null} {stepDefineForm.transformFunction === TRANSFORM_FUNCTION.LATEST ? ( - + ) : null} - - + {(stepDefineForm.transformFunction !== TRANSFORM_FUNCTION.LATEST || + stepDefineForm.latestFunctionConfig.sortFieldOptions.length > 0) && ( + <> + + + + )}
); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx index 4d2cb0e71a021..17deaa58ccb71 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx @@ -187,7 +187,8 @@ export const StepDefineSummary: FC = ({ copyToClipboardDescription={i18n.translate( 'xpack.transform.pivotPreview.copyClipboardTooltip', { - defaultMessage: 'Copy Dev Console statement of the pivot preview to the clipboard.', + defaultMessage: + 'Copy Dev Console statement of the transform preview to the clipboard.', } )} dataTestSubj="transformPivotPreview" diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index b72d70295b10f..97af8135f3899 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -485,7 +485,7 @@ export const StepDetailsForm: FC = React.memo( setCreateIndexPattern(!createIndexPattern)} @@ -528,7 +528,7 @@ export const StepDetailsForm: FC = React.memo( label={i18n.translate( 'xpack.transform.stepDetailsForm.continuousModeDateFieldLabel', { - defaultMessage: 'Date field', + defaultMessage: 'Date field for continuous mode', } )} helpText={i18n.translate( diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx index 8ee2093a1e802..4af92c2147aaa 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx @@ -23,7 +23,7 @@ export const StepDetailsTimeField: FC = ({ const noTimeFieldLabel = i18n.translate( 'xpack.transform.stepDetailsForm.noTimeFieldOptionLabel', { - defaultMessage: "I don't want to use the time filter", + defaultMessage: "I don't want to use the time field option", } ); @@ -43,7 +43,7 @@ export const StepDetailsTimeField: FC = ({ label={ } helpText={ From b93042e7e3d750d2f381b06a1eb5598666025867 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 21 Jan 2021 10:50:39 +0100 Subject: [PATCH 38/83] [APM] Exclude cypress from APM-only tsconfig (#88837) --- x-pack/plugins/apm/scripts/optimize-tsconfig/tsconfig.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/scripts/optimize-tsconfig/tsconfig.json b/x-pack/plugins/apm/scripts/optimize-tsconfig/tsconfig.json index 5a810fa90259a..4a791ed18121e 100644 --- a/x-pack/plugins/apm/scripts/optimize-tsconfig/tsconfig.json +++ b/x-pack/plugins/apm/scripts/optimize-tsconfig/tsconfig.json @@ -4,7 +4,11 @@ "./plugins/observability/**/*", "./typings/**/*" ], - "exclude": ["**/__fixtures__/**/*", "./plugins/apm/e2e/cypress/**/*"], + "exclude": [ + "**/__fixtures__/**/*", + "./plugins/apm/e2e", + "./plugins/apm/ftr_e2e" + ], "compilerOptions": { "noErrorTruncation": true } From ab1d97e4415a765b4e67ca0a973afd686e3c56c0 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 21 Jan 2021 11:00:57 +0100 Subject: [PATCH 39/83] [Lens] Restart session if fixed now becomes outdated (#88575) --- .../lens/public/app_plugin/app.test.tsx | 162 +++++++++++++++--- x-pack/plugins/lens/public/app_plugin/app.tsx | 15 +- .../lens/public/app_plugin/time_range.ts | 82 +++++++++ .../editor_frame/editor_frame.tsx | 1 - 4 files changed, 227 insertions(+), 33 deletions(-) create mode 100644 x-pack/plugins/lens/public/app_plugin/time_range.ts 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 5e38cb49114e9..5aec6b02c057c 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -10,7 +10,7 @@ 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 { EditorFrameInstance, EditorFrameProps } from '../types'; import { Document } from '../persistence'; import { DOC_TYPE } from '../../common'; import { mount } from 'enzyme'; @@ -44,6 +44,8 @@ import { import { LensAttributeService } from '../lens_attribute_service'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { EmbeddableStateTransfer } from '../../../../../src/plugins/embeddable/public'; +import { NativeRenderer } from '../native_renderer'; +import moment from 'moment'; jest.mock('../editor_frame_service/editor_frame/expression_helpers'); jest.mock('src/core/public'); @@ -144,6 +146,11 @@ function createMockTimefilter() { return unsubscribe; }, }), + calculateBounds: jest.fn(() => ({ + min: moment('2021-01-10T04:00:00.000Z'), + max: moment('2021-01-10T08:00:00.000Z'), + })), + getBounds: jest.fn(() => timeFilter), getRefreshInterval: () => {}, getRefreshIntervalDefaults: () => {}, getAutoRefreshFetch$: () => ({ @@ -233,6 +240,9 @@ describe('Lens App', () => { }), }, search: createMockSearchService(), + nowProvider: { + get: jest.fn(), + }, } as unknown) as DataPublicPluginStart, storage: { get: jest.fn(), @@ -306,8 +316,8 @@ describe('Lens App', () => { />, Object { "dateRange": Object { - "fromDate": "now-7d", - "toDate": "now", + "fromDate": "2021-01-10T04:00:00.000Z", + "toDate": "2021-01-10T08:00:00.000Z", }, "doc": undefined, "filters": Array [], @@ -350,7 +360,7 @@ describe('Lens App', () => { expect(frame.mount).toHaveBeenCalledWith( expect.any(Element), expect.objectContaining({ - dateRange: { fromDate: 'now-7d', toDate: 'now' }, + dateRange: { fromDate: '2021-01-10T04:00:00.000Z', toDate: '2021-01-10T08:00:00.000Z' }, query: { query: '', language: 'kuery' }, filters: [pinnedFilter], }) @@ -1008,7 +1018,7 @@ describe('Lens App', () => { expect(frame.mount).toHaveBeenCalledWith( expect.any(Element), expect.objectContaining({ - dateRange: { fromDate: 'now-7d', toDate: 'now' }, + dateRange: { fromDate: '2021-01-10T04:00:00.000Z', toDate: '2021-01-10T08:00:00.000Z' }, query: { query: '', language: 'kuery' }, }) ); @@ -1055,7 +1065,11 @@ describe('Lens App', () => { }); it('updates the editor frame when the user changes query or time in the search bar', () => { - const { component, frame } = mountWith({}); + const { component, frame, services } = mountWith({}); + (services.data.query.timefilter.timefilter.calculateBounds as jest.Mock).mockReturnValue({ + min: moment('2021-01-09T04:00:00.000Z'), + max: moment('2021-01-09T08:00:00.000Z'), + }); act(() => component.find(TopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-14d', to: 'now-7d' }, @@ -1071,10 +1085,14 @@ describe('Lens App', () => { }), {} ); + expect(services.data.query.timefilter.timefilter.setTime).toHaveBeenCalledWith({ + from: 'now-14d', + to: 'now-7d', + }); expect(frame.mount).toHaveBeenCalledWith( expect.any(Element), expect.objectContaining({ - dateRange: { fromDate: 'now-14d', toDate: 'now-7d' }, + dateRange: { fromDate: '2021-01-09T04:00:00.000Z', toDate: '2021-01-09T08:00:00.000Z' }, query: { query: 'new', language: 'lucene' }, }) ); @@ -1237,6 +1255,34 @@ describe('Lens App', () => { ); }); + it('clears all existing unpinned filters when the active saved query is cleared', () => { + const { component, frame, services } = mountWith({}); + act(() => + 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(() => 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({ + filters: [pinned], + }) + ); + }); + }); + + describe('search session id management', () => { it('updates the searchSessionId when the query is updated', () => { const { component, frame } = mountWith({}); act(() => { @@ -1263,12 +1309,12 @@ describe('Lens App', () => { expect(frame.mount).toHaveBeenCalledWith( expect.any(Element), expect.objectContaining({ - searchSessionId: `sessionId-1`, + searchSessionId: `sessionId-2`, }) ); }); - it('clears all existing unpinned filters when the active saved query is cleared', () => { + it('updates the searchSessionId when the active saved query is cleared', () => { const { component, frame, services } = mountWith({}); act(() => component.find(TopNavMenu).prop('onQuerySubmit')!({ @@ -1286,31 +1332,67 @@ describe('Lens App', () => { component.update(); act(() => component.find(TopNavMenu).prop('onClearSavedQuery')!()); component.update(); - expect(frame.mount).toHaveBeenLastCalledWith( + expect(frame.mount).toHaveBeenCalledWith( expect.any(Element), expect.objectContaining({ - filters: [pinned], + searchSessionId: `sessionId-2`, }) ); }); - it('updates the searchSessionId when the active saved query is cleared', () => { - const { component, frame, services } = mountWith({}); - act(() => - component.find(TopNavMenu).prop('onQuerySubmit')!({ - dateRange: { from: 'now-14d', to: 'now-7d' }, - query: { query: 'new', language: 'lucene' }, + const mockUpdate = { + filterableIndexPatterns: [], + doc: { + title: '', + description: '', + visualizationType: '', + state: { + datasourceStates: {}, + visualization: {}, + filters: [], + query: { query: '', language: 'lucene' }, + }, + references: [], + }, + isSaveable: true, + activeData: undefined, + }; + + it('does not update the searchSessionId when the state changes', () => { + const { component, frame } = mountWith({}); + act(() => { + (component.find(NativeRenderer).prop('nativeProps') as EditorFrameProps).onChange( + mockUpdate + ); + }); + component.update(); + expect(frame.mount).not.toHaveBeenCalledWith( + expect.any(Element), + expect.objectContaining({ + searchSessionId: `sessionId-2`, }) ); - 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(() => services.data.query.filterManager.setFilters([pinned, unpinned])); - component.update(); - act(() => component.find(TopNavMenu).prop('onClearSavedQuery')!()); + }); + + it('does update the searchSessionId when the state changes and too much time passed', () => { + const { component, frame, services } = mountWith({}); + + // time range is 100,000ms ago to 30,000ms ago (that's a lag of 30 percent) + (services.data.nowProvider.get as jest.Mock).mockReturnValue(new Date(Date.now() - 30000)); + (services.data.query.timefilter.timefilter.getTime as jest.Mock).mockReturnValue({ + from: 'now-2m', + to: 'now', + }); + (services.data.query.timefilter.timefilter.getBounds as jest.Mock).mockReturnValue({ + min: moment(Date.now() - 100000), + max: moment(Date.now() - 30000), + }); + + act(() => { + (component.find(NativeRenderer).prop('nativeProps') as EditorFrameProps).onChange( + mockUpdate + ); + }); component.update(); expect(frame.mount).toHaveBeenCalledWith( expect.any(Element), @@ -1319,6 +1401,34 @@ describe('Lens App', () => { }) ); }); + + it('does not update the searchSessionId when the state changes and too little time has passed', () => { + const { component, frame, services } = mountWith({}); + + // time range is 100,000ms ago to 300ms ago (that's a lag of .3 percent, not enough to trigger a session update) + (services.data.nowProvider.get as jest.Mock).mockReturnValue(new Date(Date.now() - 300)); + (services.data.query.timefilter.timefilter.getTime as jest.Mock).mockReturnValue({ + from: 'now-2m', + to: 'now', + }); + (services.data.query.timefilter.timefilter.getBounds as jest.Mock).mockReturnValue({ + min: moment(Date.now() - 100000), + max: moment(Date.now() - 300), + }); + + act(() => { + (component.find(NativeRenderer).prop('nativeProps') as EditorFrameProps).onChange( + mockUpdate + ); + }); + component.update(); + expect(frame.mount).not.toHaveBeenCalledWith( + expect.any(Element), + expect.objectContaining({ + searchSessionId: `sessionId-2`, + }) + ); + }); }); describe('showing a confirm message when leaving', () => { diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 3f10cb341105c..28e1f6da60742 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -7,7 +7,7 @@ import './app.scss'; import _ from 'lodash'; -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; import { EuiBreadcrumb } from '@elastic/eui'; @@ -39,6 +39,7 @@ import { LensByReferenceInput, LensEmbeddableInput, } from '../editor_frame_service/embeddable/embeddable'; +import { useTimeRange } from './time_range'; export function App({ history, @@ -107,9 +108,11 @@ export function App({ state.searchSessionId, ]); - // Need a stable reference for the frame component of the dateRange - const { from: fromDate, to: toDate } = data.query.timefilter.timefilter.getTime(); - const currentDateRange = useMemo(() => ({ fromDate, toDate }), [fromDate, toDate]); + const { resolvedDateRange, from: fromDate, to: toDate } = useTimeRange( + data, + state.lastKnownDoc, + setState + ); const onError = useCallback( (e: { message: string }) => @@ -658,7 +661,7 @@ export function App({ render={editorFrame.mount} nativeProps={{ searchSessionId: state.searchSessionId, - dateRange: currentDateRange, + dateRange: resolvedDateRange, query: state.query, filters: state.filters, savedQuery: state.savedQuery, @@ -670,7 +673,7 @@ export function App({ if (isSaveable !== state.isSaveable) { setState((s) => ({ ...s, isSaveable })); } - if (!_.isEqual(state.persistedDoc, doc)) { + if (!_.isEqual(state.persistedDoc, doc) && !_.isEqual(state.lastKnownDoc, doc)) { setState((s) => ({ ...s, lastKnownDoc: doc })); } if (!_.isEqual(state.activeData, activeData)) { diff --git a/x-pack/plugins/lens/public/app_plugin/time_range.ts b/x-pack/plugins/lens/public/app_plugin/time_range.ts new file mode 100644 index 0000000000000..5a9f2d5ab9e90 --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/time_range.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 './app.scss'; + +import _ from 'lodash'; +import moment from 'moment'; +import { useEffect, useMemo } from 'react'; +import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; +import { LensAppState } from './types'; +import { Document } from '../persistence'; + +function containsDynamicMath(dateMathString: string) { + return dateMathString.includes('now'); +} + +const TIME_LAG_PERCENTAGE_LIMIT = 0.02; + +/** + * Fetches the current global time range from data plugin and restarts session + * if the fixed "now" parameter is diverging too much from the actual current time. + * @param data data plugin contract to manage current now value, time range and session + * @param lastKnownDoc Current state of the editor + * @param setState state setter for Lens app state + */ +export function useTimeRange( + data: DataPublicPluginStart, + lastKnownDoc: Document | undefined, + setState: React.Dispatch> +) { + const timefilter = data.query.timefilter.timefilter; + const { from, to } = data.query.timefilter.timefilter.getTime(); + const currentNow = data.nowProvider.get(); + + // Need a stable reference for the frame component of the dateRange + const resolvedDateRange = useMemo(() => { + const { min, max } = timefilter.calculateBounds({ + from, + to, + }); + return { fromDate: min?.toISOString() || from, toDate: max?.toISOString() || to }; + // recalculate current date range if current "now" value changes because calculateBounds + // depends on it internally + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [timefilter, currentNow, from, to]); + + useEffect(() => { + const unresolvedTimeRange = timefilter.getTime(); + if ( + !containsDynamicMath(unresolvedTimeRange.from) && + !containsDynamicMath(unresolvedTimeRange.to) + ) { + return; + } + + const { min, max } = timefilter.getBounds(); + + if (!min || !max) { + // bounds not fully specified, bailing out + return; + } + + // calculate length of currently configured range in ms + const timeRangeLength = moment.duration(max.diff(min)).asMilliseconds(); + + // calculate lag of managed "now" for date math + const nowDiff = Date.now() - data.nowProvider.get().valueOf(); + + // if the lag is signifcant, start a new session to clear the cache + if (nowDiff > timeRangeLength * TIME_LAG_PERCENTAGE_LIMIT) { + setState((s) => ({ + ...s, + searchSessionId: data.search.session.start(), + })); + } + }, [data.nowProvider, data.search.session, timefilter, lastKnownDoc, setState]); + + return { resolvedDateRange, from, to }; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 2cb815596d8b9..f908d16afe470 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -252,7 +252,6 @@ export function EditorFrame(props: EditorFrameProps) { state.visualization, state.activeData, props.query, - props.dateRange, props.filters, props.savedQuery, state.title, From 05fcdd8a5674cfaa70546ef2af9b8b0fa44116bf Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 21 Jan 2021 11:31:28 +0100 Subject: [PATCH 40/83] [Search Sessions] Add searchSessionId to Vega's inspector (#88839) --- src/plugins/vis_type_vega/public/data_model/search_api.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/vis_type_vega/public/data_model/search_api.ts b/src/plugins/vis_type_vega/public/data_model/search_api.ts index 4cb2a6119a2be..2d47102705139 100644 --- a/src/plugins/vis_type_vega/public/data_model/search_api.ts +++ b/src/plugins/vis_type_vega/public/data_model/search_api.ts @@ -45,7 +45,10 @@ export class SearchAPI { }); if (this.inspectorAdapters) { - requestResponders[requestId] = this.inspectorAdapters.requests.start(requestId, request); + requestResponders[requestId] = this.inspectorAdapters.requests.start(requestId, { + ...request, + searchSessionId: this.searchSessionId, + }); requestResponders[requestId].json(params.body); } From 1709c704d9b82a3985903b1ed515ee3f46506c68 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Thu, 21 Jan 2021 10:54:28 +0000 Subject: [PATCH 41/83] [ML] Improving model snapshot revert UI experience (#88588) * [ML] Improving model snapshot revert UI experience * removing button disabling * updating component is mounted check Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../model_snapshots/model_snapshots_table.tsx | 29 +++++++---- .../revert_model_snapshot_flyout.tsx | 52 ++++++++++--------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/model_snapshots_table.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/model_snapshots_table.tsx index 5b175eb06a4a3..edf038160e0a8 100644 --- a/x-pack/plugins/ml/public/application/components/model_snapshots/model_snapshots_table.tsx +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/model_snapshots_table.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect, useCallback, useState } from 'react'; +import React, { FC, useEffect, useCallback, useState, useRef } from 'react'; import { i18n } from '@kbn/i18n'; import { @@ -50,15 +50,24 @@ export const ModelSnapshotTable: FC = ({ job, refreshJobList }) => { const [closeJobModalVisible, setCloseJobModalVisible] = useState(null); const [combinedJobState, setCombinedJobState] = useState(null); + const isMounted = useRef(true); useEffect(() => { loadModelSnapshots(); + return () => { + isMounted.current = false; + }; }, []); - const loadModelSnapshots = useCallback(async () => { + async function loadModelSnapshots() { + if (isMounted.current === false) { + // table refresh can be triggered a while after a snapshot revert has been triggered. + // ensure the table is still visible before attempted to refresh it. + return; + } const { model_snapshots: ms } = await ml.getModelSnapshots(job.job_id); setSnapshots(ms); setSnapshotsLoaded(true); - }, [job]); + } const checkJobIsClosed = useCallback( async (snapshot: ModelSnapshot) => { @@ -107,13 +116,14 @@ export const ModelSnapshotTable: FC = ({ job, refreshJobList }) => { } }, []); - const closeRevertFlyout = useCallback((reload: boolean) => { + const closeRevertFlyout = useCallback(() => { setRevertSnapshot(null); - if (reload) { - loadModelSnapshots(); - // wait half a second before refreshing the jobs list - setTimeout(refreshJobList, 500); - } + }, []); + + const refresh = useCallback(() => { + loadModelSnapshots(); + // wait half a second before refreshing the jobs list + setTimeout(refreshJobList, 500); }, []); const columns: Array> = [ @@ -231,6 +241,7 @@ export const ModelSnapshotTable: FC = ({ job, refreshJobList }) => { snapshots={snapshots} job={job} closeFlyout={closeRevertFlyout} + refresh={refresh} /> )} diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx index 62f5623f67964..05e624c194e6e 100644 --- a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx @@ -53,10 +53,17 @@ interface Props { snapshot: ModelSnapshot; snapshots: ModelSnapshot[]; job: CombinedJobWithStats; - closeFlyout(reload: boolean): void; + closeFlyout(): void; + refresh(): void; } -export const RevertModelSnapshotFlyout: FC = ({ snapshot, snapshots, job, closeFlyout }) => { +export const RevertModelSnapshotFlyout: FC = ({ + snapshot, + snapshots, + job, + closeFlyout, + refresh, +}) => { const { toasts } = useNotifications(); const { loadAnomalyDataForJob, loadEventRateForJob } = useMemo( () => chartLoaderProvider(mlResultsService), @@ -73,7 +80,6 @@ export const RevertModelSnapshotFlyout: FC = ({ snapshot, snapshots, job, const [eventRateData, setEventRateData] = useState([]); const [anomalies, setAnomalies] = useState([]); const [chartReady, setChartReady] = useState(false); - const [applying, setApplying] = useState(false); useEffect(() => { createChartData(); @@ -110,13 +116,6 @@ export const RevertModelSnapshotFlyout: FC = ({ snapshot, snapshots, job, setChartReady(true); }, [job]); - function closeWithReload() { - closeFlyout(true); - } - function closeWithoutReload() { - closeFlyout(false); - } - function showRevertModal() { setRevertModalVisible(true); } @@ -125,7 +124,6 @@ export const RevertModelSnapshotFlyout: FC = ({ snapshot, snapshots, job, } async function applyRevert() { - setApplying(true); const end = replay && runInRealTime === false ? job.data_counts.latest_record_timestamp : undefined; try { @@ -138,17 +136,19 @@ export const RevertModelSnapshotFlyout: FC = ({ snapshot, snapshots, job, })) : undefined; - await ml.jobs.revertModelSnapshot( - job.job_id, - currentSnapshot.snapshot_id, - replay, - end, - events - ); + ml.jobs + .revertModelSnapshot(job.job_id, currentSnapshot.snapshot_id, replay, end, events) + .then(() => { + toasts.addSuccess( + i18n.translate('xpack.ml.revertModelSnapshotFlyout.revertSuccessTitle', { + defaultMessage: 'Model snapshot revert successful', + }) + ); + refresh(); + }); hideRevertModal(); - closeWithReload(); + closeFlyout(); } catch (error) { - setApplying(false); toasts.addError(new Error(error.body.message), { title: i18n.translate('xpack.ml.revertModelSnapshotFlyout.revertErrorTitle', { defaultMessage: 'Model snapshot revert failed', @@ -166,7 +166,7 @@ export const RevertModelSnapshotFlyout: FC = ({ snapshot, snapshots, job, return ( <> - +
@@ -347,7 +347,7 @@ export const RevertModelSnapshotFlyout: FC = ({ snapshot, snapshots, job, - + = ({ snapshot, snapshots, job, defaultMessage: 'Apply', } )} - confirmButtonDisabled={applying} buttonColor="danger" defaultFocusedButton="confirm" - /> + > + + )} From bfcd990deede09a6d6e61449c6cef8f8dc5c12f1 Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Thu, 21 Jan 2021 12:10:10 +0100 Subject: [PATCH 42/83] Handle another node already having deleted the temporary index (#88332) * Handle another node already having deleted the temporary index * Make run_multiple_kibana_nodes.sh script more generic * Add note about dependency on jq Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../saved_objects/migrationsv2/model.test.ts | 12 ++- .../saved_objects/migrationsv2/model.ts | 20 +++-- test/scripts/run_multiple_kibana_nodes.sh | 86 +++++++++++++++++++ 3 files changed, 111 insertions(+), 7 deletions(-) create mode 100755 test/scripts/run_multiple_kibana_nodes.sh diff --git a/src/core/server/saved_objects/migrationsv2/model.test.ts b/src/core/server/saved_objects/migrationsv2/model.test.ts index 43e32fd611074..d5ab85c54a728 100644 --- a/src/core/server/saved_objects/migrationsv2/model.test.ts +++ b/src/core/server/saved_objects/migrationsv2/model.test.ts @@ -911,7 +911,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); - test('MARK_VERSION_INDEX_READY -> MARK_VERSION_INDEX_CONFLICT if someone else removed the current alias from the source index', () => { + test('MARK_VERSION_INDEX_READY -> MARK_VERSION_INDEX_CONFLICT if another removed the current alias from the source index', () => { const res: ResponseType<'MARK_VERSION_INDEX_READY'> = Either.left({ type: 'alias_not_found_exception', }); @@ -920,6 +920,16 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('MARK_VERSION_INDEX_READY -> MARK_VERSION_INDEX_CONFLICT if another node removed the temporary index', () => { + const res: ResponseType<'MARK_VERSION_INDEX_READY'> = Either.left({ + type: 'index_not_found_exception', + index: '.kibana_7.11.0_reindex_temp', + }); + const newState = model(markVersionIndexReadyState, res); + expect(newState.controlState).toEqual('MARK_VERSION_INDEX_READY_CONFLICT'); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); }); describe('MARK_VERSION_INDEX_READY_CONFLICT', () => { const aliasActions = Option.some([Symbol('alias action')] as unknown) as Option.Some< diff --git a/src/core/server/saved_objects/migrationsv2/model.ts b/src/core/server/saved_objects/migrationsv2/model.ts index ba2508bf73cce..1119edde8e268 100644 --- a/src/core/server/saved_objects/migrationsv2/model.ts +++ b/src/core/server/saved_objects/migrationsv2/model.ts @@ -638,12 +638,20 @@ export const model = (currentState: State, resW: ResponseType): // alias_not_found_exception another instance has completed a // migration from the same source. return { ...stateP, controlState: 'MARK_VERSION_INDEX_READY_CONFLICT' }; - } else if ( - left.type === 'remove_index_not_a_concrete_index' || - left.type === 'index_not_found_exception' - ) { - // We don't handle these errors as the migration algorithm will never - // cause them to occur (these are only relevant to the LEGACY_DELETE + } else if (left.type === 'index_not_found_exception') { + if (left.index === stateP.tempIndex) { + // another instance has already completed the migration and deleted + // the temporary index + return { ...stateP, controlState: 'MARK_VERSION_INDEX_READY_CONFLICT' }; + } else { + // The migration algorithm will never cause a + // index_not_found_exception for an index other than the temporary + // index handled above. + throwBadResponse(stateP, left as never); + } + } else if (left.type === 'remove_index_not_a_concrete_index') { + // We don't handle this error as the migration algorithm will never + // cause it to occur (this error is only relevant to the LEGACY_DELETE // step). throwBadResponse(stateP, left as never); } else { diff --git a/test/scripts/run_multiple_kibana_nodes.sh b/test/scripts/run_multiple_kibana_nodes.sh new file mode 100755 index 0000000000000..f5661c19bed11 --- /dev/null +++ b/test/scripts/run_multiple_kibana_nodes.sh @@ -0,0 +1,86 @@ +# 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. + +#!/bin/bash + +# +# Script to run multiple kibana nodes in parallel on the same machine. +# Make sure to run the script from kibana root directory. Some functions depend on the jq command-line utility +# being installed. +# +# bash test/scripts/run_multiple_kibana_nodes.sh [options] +# functions: +# start [instances] [args] - start multiple kibanas (3 default) +# es [args] - run elasticsearch +# tail - show logs of all kibanas +# kill - kills all started kibana processes +# clean - clean up nohup files +# kibana_index - search .kibana index against es +# + +FN="$1" + +if [ "${FN}" == "kill" ]; then + echo "killing main processes" + for pid in $(cat processes.out); do kill -9 $pid; done + echo "killing trailing processes" + for pid in $(pgrep -f scripts/kibana); do kill -9 $pid; done + exit 0; +fi + +if [ "${FN}" == "tail" ]; then + tail -f nohup_* + exit 0; +fi + +if [ "${FN}" == "clean" ]; then + rm -r nohup_*.out + rm processes.out + exit 0; +fi + +if [ "${FN}" == "es" ]; then + ARGS="$2" + yarn es snapshot $ARGS + exit 0; +fi + +if [ "${FN}" == "kibana_index" ]; then + # search the kibana index + curl -XPOST http://elastic:changeme@localhost:9200/.kibana/_search -u elastic:changeme -d '' | jq + exit 0; +fi + +if [ "${FN}" == "start" ]; then + NUM="$2" + ARGS="$3" + if test ! "${NUM-}"; then + NUM=3 + fi + node scripts/build_kibana_platform_plugins --no-examples + rm processes.out + for i in $(seq 0 $(expr $NUM - 1)) + do + PORT="56${i}1" + PROXY="56${i}3" + echo "starting kibana on port $PORT" + nohup node scripts/kibana.js --dev.basePathProxyTarget=$PROXY --server.port=$PORT --dev --no-watch --no-optimizer --no-base-path $ARGS > nohup_$i.out & + PROCESS_ID=$! + echo "${PROCESS_ID}" >> processes.out + done + exit 0; +fi From d997f207085bcd09f47f4fbf4edb7aef91c40a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Thu, 21 Jan 2021 12:13:52 +0100 Subject: [PATCH 43/83] [APM] Service overview: Introduce time-series comparison (#88665) * adding comparision select option * adding time comparison field on some pages * removing unused files * fixing unit test * adding unit tests * enabling comparison for more than 8 days * removing tooltip * refactoring search bar * moving useBreakPoint to common hooks folder, removing useShouldUSeMobileLayout hook * addressing PR comments * addressing PR comments * addressing PR comments * addressing PR comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../app/RumDashboard/RumDashboard.tsx | 2 +- .../app/TransactionDetails/index.tsx | 2 +- .../app/service_inventory/index.tsx | 2 +- .../service_inventory.test.tsx | 2 + .../components/app/service_overview/index.tsx | 8 +- .../service_overview_table_container.tsx | 6 +- .../use_should_use_mobile_layout.ts | 27 ---- .../app/transaction_overview/index.tsx | 2 +- .../transaction_overview.test.tsx | 4 +- .../components/shared/Links/url_helpers.ts | 2 + .../public/components/shared/search_bar.tsx | 39 ++++- .../shared/time_comparison/index.test.tsx | 142 +++++++++++++++++ .../shared/time_comparison/index.tsx | 144 ++++++++++++++++++ .../url_params_context/resolve_url_params.ts | 6 + .../context/url_params_context/types.ts | 2 + .../use_break_points.ts} | 0 16 files changed, 344 insertions(+), 46 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/app/service_overview/use_should_use_mobile_layout.ts create mode 100644 x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx rename x-pack/plugins/apm/public/{components/app/RumDashboard/hooks/useBreakPoints.ts => hooks/use_break_points.ts} (100%) 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 c810bd3e7c489..447f6d502fe40 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx @@ -18,7 +18,7 @@ import { UXMetrics } from './UXMetrics'; import { ImpactfulMetrics } from './ImpactfulMetrics'; import { PageLoadAndViews } from './Panels/PageLoadAndViews'; import { VisitorBreakdownsPanel } from './Panels/VisitorBreakdowns'; -import { useBreakPoints } from './hooks/useBreakPoints'; +import { useBreakPoints } from '../../../hooks/use_break_points'; import { getPercentileLabel } from './UXMetrics/translations'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index 6810b56fb8f87..b1a97dd34b887 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -113,7 +113,7 @@ export function TransactionDetails({

{transactionName}

- + diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index 8776b7e9355f1..2f83d48049486 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -129,7 +129,7 @@ export function ServiceInventory() { return ( <> - + diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index e501dd3bb7a56..eb8068bc8114d 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -57,6 +57,8 @@ function wrapper({ children }: { children?: ReactNode }) { rangeTo: 'now', start: 'mystart', end: 'myend', + comparisonEnabled: true, + comparisonType: 'yesterday', }} > {children} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index c6cc59876fe35..bf03b60d6d24f 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -12,6 +12,7 @@ import { isRumAgentName } from '../../../../common/agent_name'; import { AnnotationsContextProvider } from '../../../context/annotations/annotations_context'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; +import { useBreakPoints } from '../../../hooks/use_break_points'; import { LatencyChart } from '../../shared/charts/latency_chart'; import { TransactionBreakdownChart } from '../../shared/charts/transaction_breakdown_chart'; import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart'; @@ -22,7 +23,6 @@ import { ServiceOverviewErrorsTable } from './service_overview_errors_table'; import { ServiceOverviewInstancesChartAndTable } from './service_overview_instances_chart_and_table'; import { ServiceOverviewThroughputChart } from './service_overview_throughput_chart'; import { ServiceOverviewTransactionsTable } from './service_overview_transactions_table'; -import { useShouldUseMobileLayout } from './use_should_use_mobile_layout'; /** * The height a chart should be if it's next to a table with 5 rows and a title. @@ -44,8 +44,8 @@ export function ServiceOverview({ // The default EuiFlexGroup breaks at 768, but we want to break at 992, so we // observe the window width and set the flex directions of rows accordingly - const shouldUseMobileLayout = useShouldUseMobileLayout(); - const rowDirection = shouldUseMobileLayout ? 'column' : 'row'; + const { isMedium } = useBreakPoints(); + const rowDirection = isMedium ? 'column' : 'row'; const { transactionType } = useApmServiceContext(); const transactionTypeLabel = i18n.translate( @@ -57,7 +57,7 @@ export function ServiceOverview({ return ( - + {isRumAgent && ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_table_container.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_table_container.tsx index 76db81a70550d..bbd6dd1d498d3 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_table_container.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_table_container.tsx @@ -6,7 +6,7 @@ import React, { ReactNode } from 'react'; import styled from 'styled-components'; -import { useShouldUseMobileLayout } from './use_should_use_mobile_layout'; +import { useBreakPoints } from '../../../hooks/use_break_points'; /** * The height for a table on the overview page. Is the height of a 5-row basic @@ -58,12 +58,12 @@ export function ServiceOverviewTableContainer({ children?: ReactNode; isEmptyAndLoading: boolean; }) { - const shouldUseMobileLayout = useShouldUseMobileLayout(); + const { isMedium } = useBreakPoints(); return ( {children} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/use_should_use_mobile_layout.ts b/x-pack/plugins/apm/public/components/app/service_overview/use_should_use_mobile_layout.ts deleted file mode 100644 index bd844a3f2e694..0000000000000 --- a/x-pack/plugins/apm/public/components/app/service_overview/use_should_use_mobile_layout.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { isWithinMaxBreakpoint } from '@elastic/eui'; -import { useEffect, useState } from 'react'; - -export function useShouldUseMobileLayout() { - const [shouldUseMobileLayout, setShouldUseMobileLayout] = useState( - isWithinMaxBreakpoint(window.innerWidth, 'm') - ); - - useEffect(() => { - const resizeHandler = () => { - setShouldUseMobileLayout(isWithinMaxBreakpoint(window.innerWidth, 'm')); - }; - window.addEventListener('resize', resizeHandler); - - return () => { - window.removeEventListener('resize', resizeHandler); - }; - }); - - return shouldUseMobileLayout; -} diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 30fbfe9cc8708..5be08477b3bf5 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -108,7 +108,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { return ( <> - + diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx index bfb9c51bc245e..31d90330721da 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx @@ -130,7 +130,7 @@ describe('TransactionOverview', () => { }); expect(history.location.search).toEqual( - '?transactionType=secondType&rangeFrom=now-15m&rangeTo=now' + '?transactionType=secondType&rangeFrom=now-15m&rangeTo=now&comparisonEnabled=true&comparisonType=yesterday' ); expect(getByText(container, 'firstType')).toBeInTheDocument(); expect(getByText(container, 'secondType')).toBeInTheDocument(); @@ -139,7 +139,7 @@ describe('TransactionOverview', () => { expect(history.push).toHaveBeenCalled(); expect(history.location.search).toEqual( - '?transactionType=firstType&rangeFrom=now-15m&rangeTo=now' + '?transactionType=firstType&rangeFrom=now-15m&rangeTo=now&comparisonEnabled=true&comparisonType=yesterday' ); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/Links/url_helpers.ts b/x-pack/plugins/apm/public/components/shared/Links/url_helpers.ts index 8576d9ee86353..e899e4a07e96f 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/url_helpers.ts +++ b/x-pack/plugins/apm/public/components/shared/Links/url_helpers.ts @@ -85,6 +85,8 @@ export type APMQueryParams = { searchTerm?: string; percentile?: 50 | 75 | 90 | 95 | 99; latencyAggregationType?: string; + comparisonEnabled?: boolean; + comparisonType?: string; } & { [key in LocalUIFilterName]?: string }; // forces every value of T[K] to be type: string diff --git a/x-pack/plugins/apm/public/components/shared/search_bar.tsx b/x-pack/plugins/apm/public/components/shared/search_bar.tsx index 6382f4937ac0e..48d329a853327 100644 --- a/x-pack/plugins/apm/public/components/shared/search_bar.tsx +++ b/x-pack/plugins/apm/public/components/shared/search_bar.tsx @@ -7,22 +7,49 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; +import { px, unit } from '../../style/variables'; import { DatePicker } from './DatePicker'; import { KueryBar } from './KueryBar'; +import { TimeComparison } from './time_comparison'; +import { useBreakPoints } from '../../hooks/use_break_points'; const SearchBarFlexGroup = styled(EuiFlexGroup)` margin: ${({ theme }) => `${theme.eui.euiSizeM} ${theme.eui.euiSizeM} -${theme.eui.gutterTypes.gutterMedium} ${theme.eui.euiSizeM}`}; `; -export function SearchBar(props: { prepend?: React.ReactNode | string }) { +interface Props { + prepend?: React.ReactNode | string; + showTimeComparison?: boolean; +} + +function getRowDirection(showColumn: boolean) { + return showColumn ? 'column' : 'row'; +} + +export function SearchBar({ prepend, showTimeComparison = false }: Props) { + const { isMedium, isLarge } = useBreakPoints(); + const itemsStyle = { marginBottom: isLarge ? px(unit) : 0 }; return ( - - - + + + - - + + + {showTimeComparison && ( + + + + )} + + + + ); diff --git a/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx b/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx new file mode 100644 index 0000000000000..6348097a3e3ad --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { render } from '@testing-library/react'; +import React, { ReactNode } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { EuiThemeProvider } from '../../../../../observability/public'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import { IUrlParams } from '../../../context/url_params_context/types'; +import { + expectTextsInDocument, + expectTextsNotInDocument, +} from '../../../utils/testHelpers'; +import { TimeComparison } from './'; +import * as urlHelpers from '../../shared/Links/url_helpers'; + +function getWrapper(params?: IUrlParams) { + return ({ children }: { children?: ReactNode }) => { + return ( + + + {children} + + + ); + }; +} + +describe('TimeComparison', () => { + const spy = jest.spyOn(urlHelpers, 'replace'); + beforeEach(() => { + jest.resetAllMocks(); + }); + describe('Time range is between 0 - 24 hours', () => { + it('sets default values', () => { + const Wrapper = getWrapper({ + start: '2021-01-28T14:45:00.000Z', + end: '2021-01-28T15:00:00.000Z', + }); + render(, { + wrapper: Wrapper, + }); + expect(spy).toHaveBeenCalledWith(expect.anything(), { + query: { + comparisonEnabled: 'true', + comparisonType: 'yesterday', + }, + }); + }); + it('selects yesterday and enables comparison', () => { + const Wrapper = getWrapper({ + start: '2021-01-28T14:45:00.000Z', + end: '2021-01-28T15:00:00.000Z', + comparisonEnabled: true, + comparisonType: 'yesterday', + }); + const component = render(, { + wrapper: Wrapper, + }); + expectTextsInDocument(component, ['Yesterday', 'A week ago']); + expect( + (component.getByTestId('comparisonSelect') as HTMLSelectElement) + .selectedIndex + ).toEqual(0); + }); + }); + + describe('Time range is between 24 hours - 1 week', () => { + it('sets default values', () => { + const Wrapper = getWrapper({ + start: '2021-01-26T15:00:00.000Z', + end: '2021-01-28T15:00:00.000Z', + }); + render(, { + wrapper: Wrapper, + }); + expect(spy).toHaveBeenCalledWith(expect.anything(), { + query: { + comparisonEnabled: 'true', + comparisonType: 'week', + }, + }); + }); + it('selects week and enables comparison', () => { + const Wrapper = getWrapper({ + start: '2021-01-26T15:00:00.000Z', + end: '2021-01-28T15:00:00.000Z', + comparisonEnabled: true, + comparisonType: 'week', + }); + const component = render(, { + wrapper: Wrapper, + }); + expectTextsNotInDocument(component, ['Yesterday']); + expectTextsInDocument(component, ['A week ago']); + expect( + (component.getByTestId('comparisonSelect') as HTMLSelectElement) + .selectedIndex + ).toEqual(0); + }); + }); + + describe('Time range is greater than 7 days', () => { + it('Shows absolute times without year when within the same year', () => { + const Wrapper = getWrapper({ + start: '2021-01-20T15:00:00.000Z', + end: '2021-01-28T15:00:00.000Z', + comparisonEnabled: true, + comparisonType: 'previousPeriod', + }); + const component = render(, { + wrapper: Wrapper, + }); + expect(spy).not.toHaveBeenCalled(); + expectTextsInDocument(component, ['20/01 - 28/01']); + expect( + (component.getByTestId('comparisonSelect') as HTMLSelectElement) + .selectedIndex + ).toEqual(0); + }); + + it('Shows absolute times with year when on different year', () => { + const Wrapper = getWrapper({ + start: '2020-12-20T15:00:00.000Z', + end: '2021-01-28T15:00:00.000Z', + comparisonEnabled: true, + comparisonType: 'previousPeriod', + }); + const component = render(, { + wrapper: Wrapper, + }); + expect(spy).not.toHaveBeenCalled(); + expectTextsInDocument(component, ['20/12/20 - 28/01/21']); + expect( + (component.getByTestId('comparisonSelect') as HTMLSelectElement) + .selectedIndex + ).toEqual(0); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx new file mode 100644 index 0000000000000..2195621167e83 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx @@ -0,0 +1,144 @@ +/* + * 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 { EuiCheckbox, EuiSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import styled from 'styled-components'; +import { getDateDifference } from '../../../../common/utils/formatters'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { px, unit } from '../../../style/variables'; +import * as urlHelpers from '../../shared/Links/url_helpers'; +import { useBreakPoints } from '../../../hooks/use_break_points'; + +const PrependContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + background-color: ${({ theme }) => theme.eui.euiGradientMiddle}; + padding: 0 ${px(unit)}; +`; + +function formatPreviousPeriodDates({ + momentStart, + momentEnd, +}: { + momentStart: moment.Moment; + momentEnd: moment.Moment; +}) { + const isDifferentYears = momentStart.get('year') !== momentEnd.get('year'); + const dateFormat = isDifferentYears ? 'DD/MM/YY' : 'DD/MM'; + return `${momentStart.format(dateFormat)} - ${momentEnd.format(dateFormat)}`; +} + +function getSelectOptions({ start, end }: { start?: string; end?: string }) { + const momentStart = moment(start); + const momentEnd = moment(end); + const dateDiff = getDateDifference(momentStart, momentEnd, 'days'); + + const yesterdayOption = { + value: 'yesterday', + text: i18n.translate('xpack.apm.timeComparison.select.yesterday', { + defaultMessage: 'Yesterday', + }), + }; + + const aWeekAgoOption = { + value: 'week', + text: i18n.translate('xpack.apm.timeComparison.select.weekAgo', { + defaultMessage: 'A week ago', + }), + }; + + const prevPeriodOption = { + value: 'previousPeriod', + text: formatPreviousPeriodDates({ momentStart, momentEnd }), + }; + + // Less than one day + if (dateDiff < 1) { + return [yesterdayOption, aWeekAgoOption]; + } + + // Less than one week + if (dateDiff <= 7) { + return [aWeekAgoOption]; + } + + // above one week + return [prevPeriodOption]; +} + +export function TimeComparison() { + const history = useHistory(); + const { isMedium, isLarge } = useBreakPoints(); + const { + urlParams: { start, end, comparisonEnabled, comparisonType }, + } = useUrlParams(); + + const selectOptions = getSelectOptions({ start, end }); + + // Sets default values + if (comparisonEnabled === undefined || comparisonType === undefined) { + urlHelpers.replace(history, { + query: { + comparisonEnabled: comparisonEnabled === false ? 'false' : 'true', + comparisonType: comparisonType + ? comparisonType + : selectOptions[0].value, + }, + }); + return null; + } + + const isSelectedComparisonTypeAvailable = selectOptions.some( + ({ value }) => value === comparisonType + ); + + // Replaces type when current one is no longer available in the select options + if (selectOptions.length !== 0 && !isSelectedComparisonTypeAvailable) { + urlHelpers.replace(history, { + query: { comparisonType: selectOptions[0].value }, + }); + return null; + } + + return ( + + 0} + onChange={() => { + urlHelpers.push(history, { + query: { + comparisonEnabled: Boolean(!comparisonEnabled).toString(), + }, + }); + }} + /> + + } + onChange={(e) => { + urlHelpers.push(history, { + query: { + comparisonType: e.target.value, + }, + }); + }} + /> + ); +} diff --git a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts index 0596d649116a0..1b8a131bd88a3 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts @@ -49,6 +49,8 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { searchTerm, percentile, latencyAggregationType = LatencyAggregationType.avg, + comparisonEnabled, + comparisonType, } = query; const localUIFilters = pickKeys(query, ...localUIFilterNames); @@ -78,6 +80,10 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { searchTerm: toString(searchTerm), percentile: toNumber(percentile), latencyAggregationType, + comparisonEnabled: comparisonEnabled + ? toBoolean(comparisonEnabled) + : undefined, + comparisonType, // ui filters environment, diff --git a/x-pack/plugins/apm/public/context/url_params_context/types.ts b/x-pack/plugins/apm/public/context/url_params_context/types.ts index d792c93b7d0dc..cd5fa55cd132f 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/types.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/types.ts @@ -29,4 +29,6 @@ export type IUrlParams = { searchTerm?: string; percentile?: number; latencyAggregationType?: string; + comparisonEnabled?: boolean; + comparisonType?: string; } & Partial>; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useBreakPoints.ts b/x-pack/plugins/apm/public/hooks/use_break_points.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useBreakPoints.ts rename to x-pack/plugins/apm/public/hooks/use_break_points.ts From dfd96d62c42207dd4b700a63548428aaded282af Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 21 Jan 2021 12:09:19 +0000 Subject: [PATCH 44/83] ebsure we always select the correct index (#88876) At times we find the driver controlling the ComboBox in our UI tests can select the wrong item, this ensures we always select the correct index in the Connector tests. --- .../apps/triggers_actions_ui/connectors.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index ab2270b4ce70f..72f325bfc2d6f 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -17,8 +17,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const comboBox = getService('comboBox'); const supertest = getService('supertest'); - // FLAKY: https://github.com/elastic/kibana/issues/88796 - describe.skip('Connectors', function () { + describe('Connectors', function () { const objectRemover = new ObjectRemover(supertest); before(async () => { @@ -285,7 +284,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.setValue('nameInput', connectorName); - await comboBox.set('connectorIndexesComboBox', indexName); + await retry.try(async () => { + // At times we find the driver controlling the ComboBox in tests + // can select the wrong item, this ensures we always select the correct index + await comboBox.set('connectorIndexesComboBox', indexName); + expect( + await comboBox.isOptionSelected( + await testSubjects.find('connectorIndexesComboBox'), + indexName + ) + ).to.be(true); + }); await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)'); await pageObjects.common.closeToast(); From 248ed420e2f191d7b476c1b9484b5d9b2537f8a3 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 21 Jan 2021 14:05:32 +0100 Subject: [PATCH 45/83] [Lens] Upgrade fixtures (#88838) --- .../es_archives/visualize/default/data.json | 205 +- .../visualize/default/mappings.json | 2986 ++++++++++++++--- 2 files changed, 2709 insertions(+), 482 deletions(-) diff --git a/x-pack/test/functional/es_archives/visualize/default/data.json b/x-pack/test/functional/es_archives/visualize/default/data.json index 5b5ee355c7086..fe29bad0fa381 100644 --- a/x-pack/test/functional/es_archives/visualize/default/data.json +++ b/x-pack/test/functional/es_archives/visualize/default/data.json @@ -1,20 +1,22 @@ { "type": "doc", "value": { - "index": ".kibana", - "type": "doc", "id": "space:default", + "index": ".kibana_1", "source": { + "migrationVersion": { + "space": "6.6.0" + }, + "references": [ + ], "space": { - "name": "Default", + "_reserved": true, "description": "This is the default space!", - "disabledFeatures": [], - "_reserved": true + "disabledFeatures": [ + ], + "name": "Default" }, - "type": "space", - "migrationVersion": { - "space": "6.6.0" - } + "type": "space" } } } @@ -97,24 +99,25 @@ } } + { "type": "doc", "value": { - "index" : ".kibana", - "id" : "index-pattern:metricbeat-*", - "type": "_doc", - "source" : { - "index-pattern" : { - "fieldFormatMap" : "{\"aerospike.namespace.device.available.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.device.free.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.device.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.device.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.free.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.memory.used.data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.index.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.sindex.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"aws.rds.disk_usage.bin_log.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.free_local_storage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.free_storage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.freeable_memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.latency.commit\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.ddl\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.dml\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.insert\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.read\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.select\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.update\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.write\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.replica_lag.sec\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.swap_usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.volume_used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_daily_storage.bucket.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.downloaded.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.latency.first_byte.ms\":{\"id\":\"duration\",\"params\":{}},\"aws.s3_request.latency.total_request.ms\":{\"id\":\"duration\",\"params\":{}},\"aws.s3_request.requests.select_returned.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.requests.select_scanned.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.uploaded.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.sqs.oldest_message_age.sec\":{\"id\":\"duration\",\"params\":{}},\"aws.sqs.sent_message_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.degraded.ratio\":{\"id\":\"percent\",\"params\":{}},\"ceph.cluster_status.misplace.ratio\":{\"id\":\"percent\",\"params\":{}},\"ceph.cluster_status.pg.avail_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.data_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.total_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.used_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.traffic.read_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.traffic.write_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.log.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.misc.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.sst.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.total.byte\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.used.byte\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.used.pct\":{\"id\":\"percent\",\"params\":{}},\"ceph.pool_disk.stats.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.pool_disk.stats.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"client.bytes\":{\"id\":\"bytes\",\"params\":{}},\"client.nat.port\":{\"id\":\"string\",\"params\":{}},\"client.port\":{\"id\":\"string\",\"params\":{}},\"coredns.stats.dns.request.duration.ns.sum\":{\"id\":\"duration\",\"params\":{}},\"couchbase.bucket.data.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.disk.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.quota.ram.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.quota.use.pct\":{\"id\":\"percent\",\"params\":{}},\"couchbase.cluster.hdd.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.quota.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.used.by_data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.total.per_node.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.total.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.used.per_node.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.used.by_data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.couch.docs.data_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.couch.docs.disk_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.mcd_memory.allocated.bytes\":{\"id\":\"bytes\",\"params\":{}},\"destination.bytes\":{\"id\":\"bytes\",\"params\":{}},\"destination.nat.port\":{\"id\":\"string\",\"params\":{}},\"destination.port\":{\"id\":\"string\",\"params\":{}},\"docker.cpu.core.*.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.kernel.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.system.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.user.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.diskio.summary.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.commit.peak\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.commit.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.limit\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.private_working_set.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.rss.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.memory.rss.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.usage.max\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.usage.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.memory.usage.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.inbound.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.outbound.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.primaries.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.primaries.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.total.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.total.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.total.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.total.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.heap.init.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.heap.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.nonheap.init.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.nonheap.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.indices.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.disk.mvcc_db_total_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.memory.go_memstats_alloc.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.network.client_grpc_received.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.network.client_grpc_sent.bytes\":{\"id\":\"bytes\",\"params\":{}},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\",\"params\":{}},\"event.severity\":{\"id\":\"string\",\"params\":{}},\"golang.heap.allocations.active\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.allocated\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.idle\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.total\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.gc.next_gc_limit\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.obtained\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.released\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.stack\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.total\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.info.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"haproxy.info.memory.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.info.ssl.frontend.session_reuse.pct\":{\"id\":\"percent\",\"params\":{}},\"haproxy.stat.compressor.bypassed.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.response.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.throttle.pct\":{\"id\":\"percent\",\"params\":{}},\"http.request.body.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.request.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.body.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.status_code\":{\"id\":\"string\",\"params\":{}},\"kibana.stats.process.memory.heap.size_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kibana.stats.process.memory.heap.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kibana.stats.process.memory.heap.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.cpu.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.cpu.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.logs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.logs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.logs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.request.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.memory.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.allocatable.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.network.rx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.network.tx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.cpu.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.cpu.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.working_set.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.network.rx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.network.tx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.avg_obj_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.data_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.extent_free_list.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.file_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.index_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.storage_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.replstatus.headroom.max\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.headroom.min\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.lag.max\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.lag.min\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.oplog.size.allocated\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.replstatus.oplog.size.used\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.extra_info.heap_usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.dirty.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.maximum.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.max_file_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mysql.status.bytes.received\":{\"id\":\"bytes\",\"params\":{}},\"mysql.status.bytes.sent\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.cpu\":{\"id\":\"percent\",\"params\":{}},\"nats.stats.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.mem.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.uptime\":{\"id\":\"duration\",\"params\":{}},\"nats.subscriptions.cache.hit_rate\":{\"id\":\"percent\",\"params\":{}},\"network.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"process.pgid\":{\"id\":\"string\",\"params\":{}},\"process.pid\":{\"id\":\"string\",\"params\":{}},\"process.ppid\":{\"id\":\"string\",\"params\":{}},\"process.thread.id\":{\"id\":\"string\",\"params\":{}},\"rabbitmq.connection.frame_max\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.disk.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.disk.free.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.gc.reclaimed.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.io.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.io.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.mem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.queue.consumers.utilisation.pct\":{\"id\":\"percent\",\"params\":{}},\"rabbitmq.queue.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.active\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.allocated\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.fragmentation.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.resident\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.fragmentation.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.max.value\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.dataset\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.lua\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.peak\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.rss\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.value\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.buffer.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.copy_on_write.last_size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.rewrite.buffer.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.rewrite.current_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.aof.rewrite.last_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.aof.size.base\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.size.current\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.rdb.bgsave.current_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.rdb.bgsave.last_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.rdb.copy_on_write.last_size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.replication.backlog.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.replication.master.last_io_seconds_ago\":{\"id\":\"duration\",\"params\":{}},\"redis.info.replication.master.sync.last_io_seconds_ago\":{\"id\":\"duration\",\"params\":{}},\"redis.info.replication.master.sync.left_bytes\":{\"id\":\"bytes\",\"params\":{}},\"server.bytes\":{\"id\":\"bytes\",\"params\":{}},\"server.nat.port\":{\"id\":\"string\",\"params\":{}},\"server.port\":{\"id\":\"string\",\"params\":{}},\"source.bytes\":{\"id\":\"bytes\",\"params\":{}},\"source.nat.port\":{\"id\":\"string\",\"params\":{}},\"source.port\":{\"id\":\"string\",\"params\":{}},\"system.core.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.iowait.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.irq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.nice.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.softirq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.steal.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.system.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.user.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.idle.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.iowait.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.iowait.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.irq.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.irq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.nice.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.nice.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.softirq.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.softirq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.steal.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.steal.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.system.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.system.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.total.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.user.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.user.pct\":{\"id\":\"percent\",\"params\":{}},\"system.diskio.iostat.read.per_sec.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.iostat.write.per_sec.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.entropy.pct\":{\"id\":\"percent\",\"params\":{}},\"system.filesystem.available\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.free\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.total\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.fsstat.total_size.free\":{\"id\":\"bytes\",\"params\":{}},\"system.fsstat.total_size.total\":{\"id\":\"bytes\",\"params\":{}},\"system.fsstat.total_size.used\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.default_size\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.free\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.reserved\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.surplus\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.total\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.swap.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.total\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.total\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.blkio.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.active_anon.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.active_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.cache.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.hierarchical_memory_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.hierarchical_memsw_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.inactive_anon.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.inactive_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.mapped_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.rss_huge.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.swap.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.unevictable.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.memory.rss.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.memory.share\":{\"id\":\"bytes\",\"params\":{}},\"system.process.memory.size\":{\"id\":\"bytes\",\"params\":{}},\"system.socket.summary.tcp.memory\":{\"id\":\"bytes\",\"params\":{}},\"system.socket.summary.udp.memory\":{\"id\":\"bytes\",\"params\":{}},\"system.uptime.duration.ms\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\"}},\"url.port\":{\"id\":\"string\",\"params\":{}},\"vsphere.datastore.capacity.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.used.pct\":{\"id\":\"percent\",\"params\":{}},\"vsphere.host.memory.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.host.memory.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.host.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.free.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.total.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.used.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.used.host.bytes\":{\"id\":\"bytes\",\"params\":{}},\"windows.service.uptime.ms\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\"}}}", - "fields" : "[{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"aerospike.namespace.client.delete.error\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.delete.not_found\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.delete.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.delete.timeout\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.read.error\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.read.not_found\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.read.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.read.timeout\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.write.error\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.write.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.write.timeout\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.device.available.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.device.free.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.device.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.device.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.hwm_breached\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.memory.free.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.memory.used.data.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.memory.used.index.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.memory.used.sindex.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.memory.used.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.node.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.objects.master\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.objects.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.stop_writes\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.ephemeral_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.bytes_per_request\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.bytes_per_sec\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.connections.async.closing\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.connections.async.keep_alive\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.connections.async.writing\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.connections.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.cpu.children_system\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.cpu.children_user\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.cpu.load\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.cpu.system\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.cpu.user\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.load.1\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.load.15\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.load.5\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.requests_per_sec\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.closing_connection\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.dns_lookup\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.gracefully_finishing\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.idle_cleanup\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.keepalive\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.logging\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.open_slot\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.reading_request\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.sending_reply\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.starting_up\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.waiting_for_connection\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.total_accesses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.total_kbytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.uptime.server_uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.uptime.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.workers.busy\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.workers.idle\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.cloudwatch.namespace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.cpu.credit_balance\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.cpu.credit_usage\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.cpu.surplus_credit_balance\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.cpu.surplus_credits_charged\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.cpu.total.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.read.bytes_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.read.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.read.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.write.bytes_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.write.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.write.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.core.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.image.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.monitoring.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.private.dns_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.private.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.public.dns_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.public.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.state.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.state.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.threads_per_core\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.in.bytes_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.in.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.in.packets_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.out.bytes_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.out.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.out.packets_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.status.check_failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.status.check_failed_instance\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.status.check_failed_system\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.cpu.credit_balance\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.cpu.credit_usage\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.cpu.total.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.database_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.db_instance.arn\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.db_instance.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.db_instance.identifier\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.db_instance.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.deadlocks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.disk_queue_depth\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.disk_usage.bin_log.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.disk_usage.replication_slot.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.disk_usage.transaction_logs.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.failed_sql_server_agent_jobs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.free_local_storage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.free_storage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.freeable_memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.commit\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.ddl\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.dml\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.insert\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.read\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.select\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.update\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.write\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.login_failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.maximum_used_transaction_ids\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.oldest_replication_slot_lag.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.queries\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.read_io.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.replica_lag.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.swap_usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.commit\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.ddl\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.delete\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.dml\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.insert\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.network\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.network_receive\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.network_transmit\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.read\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.select\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.update\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.write\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.transaction_logs_generation\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.transactions.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.transactions.blocked\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.volume_used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.write_io.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_daily_storage.bucket.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_daily_storage.bucket.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_daily_storage.number_of_objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.bucket.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.downloaded.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.errors.4xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.errors.5xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.latency.first_byte.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.latency.total_request.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.delete\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.get\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.head\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.list\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.post\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.put\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.select\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.select_returned.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.select_scanned.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.uploaded.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.empty_receives\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.delayed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.not_visible\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.visible\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.oldest_message_age.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.sent_message_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.state.management.enabled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.state.module.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.state.output.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.state.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.acked\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.batches\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.duplicates\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.toomany\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.read.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.write.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.runtime.goroutines\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.uptime.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_disk.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_disk.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_disk.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_health.overall_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_health.timechecks.epoch\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_health.timechecks.round.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_health.timechecks.round.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.degraded.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.degraded.ratio\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.degraded.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.misplace.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.misplace.ratio\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.misplace.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.epoch\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.full\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.nearfull\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.num_in_osds\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.num_osds\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.num_remapped_pgs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.num_up_osds\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg.avail_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg.data_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg.total_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg.used_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg_state.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg_state.state_name\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg_state.version\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.traffic.read_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.traffic.read_op_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.traffic.write_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.traffic.write_op_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.version\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.available.kb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.available.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.health\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.last_updated\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.store_stats.last_updated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.store_stats.log.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.store_stats.misc.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.store_stats.sst.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.store_stats.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.total.kb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.used.kb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.device_class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.pg_num\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.total.byte\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.used.byte\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.used.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.children\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.crush_weight\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.depth\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.device_class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.father\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.primary_affinity\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.reweight\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.type_id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.stats.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.stats.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.stats.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.stats.used.kb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.account.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.availability_zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.image.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.machine.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.project.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.provider\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.region\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.autopilot.healthy\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.alloc.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.garbage_collector.pause.current.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.garbage_collector.pause.total.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.garbage_collector.runs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.goroutines\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.heap_objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.malloc_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.sys.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.tag\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.runtime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.cache.hits.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.cache.misses.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.do.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.duration.ns.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.duration.ns.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.type.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.response.rcode.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.response.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.response.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.panic.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.proto\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.rcode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.server\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.data.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.disk.fetches\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.disk.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.item_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.memory.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.quota.ram.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.quota.use.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.hdd.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.hdd.quota.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.hdd.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.hdd.used.by_data.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.hdd.used.value.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.max_bucket_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.quota.index_memory.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.quota.memory.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.quota.total.per_node.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.quota.total.value.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.quota.used.per_node.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.quota.used.value.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.used.by_data.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.used.value.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.cmd_get\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.docs.data_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.docs.disk_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.spatial.data_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.spatial.disk_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.views.data_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.views.disk_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.cpu_utilization_rate.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.current_items.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.current_items.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.ep_bg_fetched\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.get_hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.mcd_memory.allocated.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.mcd_memory.reserved.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.memory.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.memory.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.memory.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.swap.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.swap.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.uptime.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.vb_replica_curr_items\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.auth_cache_hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.auth_cache_misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.database_reads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.database_writes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.open_databases\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.open_os_files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.request_time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd.bulk_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd.clients_requesting_changes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd.temporary_view_reads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd.view_reads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.COPY\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.DELETE\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.GET\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.HEAD\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.POST\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.PUT\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.200\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.201\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.202\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.301\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.304\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.400\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.401\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.403\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.404\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.405\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.409\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.412\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.500\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.data\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.ttl\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.header_flags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.op_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.resolved_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.response_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.command\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.ip_addresses\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.size.root_fs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.size.rw\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.tags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.kernel.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.kernel.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.system.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.system.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.total.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.user.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.user.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.read.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.read.rate\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.reads\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.summary.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.summary.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.summary.rate\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.total\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.write.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.write.rate\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.writes\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.actor.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.from\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.event.end_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.event.exit_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.event.output\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.event.start_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.failingstreak\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.id.current\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.id.parent\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.size.regular\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.size.virtual\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.tags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.containers.paused\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.containers.running\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.containers.stopped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.containers.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.images\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.commit.peak\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.commit.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.fail.count\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.private_working_set.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.rss.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.rss.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.usage.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.usage.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.usage.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.in.dropped\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.in.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.in.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.inbound.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.inbound.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.inbound.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.inbound.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.interface\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.out.dropped\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.out.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.out.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.outbound.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.outbound.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.outbound.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.outbound.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ecs.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.follower.global_checkpoint\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.follower.index\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.follower.operations_written\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.follower.shard.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.follower.time_since_last_read.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.leader.index\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.leader.max_seq_no\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.pending_task.insert_order\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.pending_task.priority\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.pending_task.source\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.pending_task.time_in_queue.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.state.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.indices.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.indices.fielddata.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.indices.shards.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.indices.shards.primaries\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.nodes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.nodes.data\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.nodes.master\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.primary\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.source.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.source.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.source.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.stage\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.target.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.target.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.target.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.primaries.docs.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.primaries.docs.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.primaries.segments.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.primaries.segments.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.primaries.store.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.total.docs.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.total.docs.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.total.segments.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.total.segments.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.total.store.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.total.docs.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.total.docs.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.total.segments.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.total.segments.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.total.store.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ml.job.data_counts.invalid_date_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ml.job.data_counts.processed_record_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ml.job.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ml.job.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.jvm.memory.heap.init.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.jvm.memory.heap.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.jvm.memory.nonheap.init.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.jvm.memory.nonheap.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.jvm.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.process.mlockall\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.fs.summary.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.fs.summary.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.fs.summary.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.indices.docs.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.indices.docs.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.indices.segments.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.indices.segments.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.indices.store.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.gc.collectors.old.collection.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.gc.collectors.old.collection.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.gc.collectors.young.collection.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.gc.collectors.young.collection.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.old.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.old.peak.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.old.peak_max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.old.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.survivor.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak_max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.survivor.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.young.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.young.peak.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.young.peak_max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.young.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.shard.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.shard.primary\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.shard.relocating_node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.shard.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.cluster_manager.active_clusters\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.cluster_manager.cluster_added\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.cluster_manager.cluster_modified\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.cluster_manager.cluster_removed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.cluster_manager.warming_clusters\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.filesystem.flushed_by_timer\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.filesystem.reopen_failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.filesystem.write_buffered\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.filesystem.write_completed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.filesystem.write_total_buffered\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.header_overflow\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.headers_cb_no_stream\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.rx_messaging_error\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.rx_reset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.too_many_header_frames\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.trailers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.tx_reset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.listener_added\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.listener_create_failure\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.listener_create_success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.listener_modified\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.listener_removed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.total_listeners_active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.total_listeners_draining\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.total_listeners_warming\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.admin_overrides_active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.load_error\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.load_success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.num_keys\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.override_dir_exists\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.override_dir_not_exists\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.days_until_first_cert_expiring\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.hot_restart_epoch\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.live\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.memory_allocated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.memory_heap_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.parent_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.total_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.version\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.watchdog_mega_miss\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.watchdog_miss\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.stats.overflow\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.api_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.disk.backend_commit_duration.ns.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.disk.backend_commit_duration.ns.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.disk.mvcc_db_total_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.disk.wal_fsync_duration.ns.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.disk.wal_fsync_duration.ns.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.counts.followers.counts.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.counts.followers.counts.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.latency.follower.latency.standardDeviation\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.latency.followers.latency.average\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.latency.followers.latency.current\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.latency.followers.latency.maximum\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.latency.followers.latency.minimum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.leader\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.memory.go_memstats_alloc.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.network.client_grpc_received.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.network.client_grpc_sent.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.leaderinfo.leader\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.leaderinfo.starttime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.leaderinfo.uptime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.recv.appendrequest.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.recv.bandwidthrate\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.recv.pkgrate\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.send.appendrequest.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.send.bandwidthrate\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.send.pkgrate\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.starttime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.grpc_handled.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.grpc_started.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.has_leader\",\"type\":\"number\",\"esTypes\":[\"byte\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.leader_changes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.proposals_committed.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.proposals_failed.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.proposals_pending.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.compareanddelete.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.compareanddelete.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.compareandswap.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.compareandswap.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.create.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.create.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.delete.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.delete.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.expire.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.gets.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.gets.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.sets.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.sets.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.update.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.update.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.watchers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.dataset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.end\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.kind\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.module\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.outcome\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.provider\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.risk_score\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.risk_score_norm\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.sequence\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.severity\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.timezone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.accessed\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.ctime\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.device\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.extension\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.gid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.group\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.inode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mtime\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.owner\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.target_path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.expvar.cmdline\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.allocated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.frees\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.idle\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.mallocs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.cmdline\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.cpu_fraction\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.next_gc_limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.pause.avg.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.pause.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.pause.max.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.pause.sum.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.total_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.total_pause.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.obtained\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.released\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.stack\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"graphite.server.example\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.compress.bps.in\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.compress.bps.out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.compress.bps.rate_limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.hard_max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.rate.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.ssl.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.ssl.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.ssl.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.idle.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.memory.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.pipes.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.pipes.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.pipes.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.process_num\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.processes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.requests.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.requests.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.run_queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.session.rate.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.session.rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.session.rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.sockets.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.backend.key_rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.backend.key_rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.cache_misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.cached_lookups\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.frontend.key_rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.frontend.key_rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.frontend.session_reuse.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.rate.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.tasks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ulimit_n\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.uptime.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.zlib_mem_usage.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.zlib_mem_usage.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.agent.last\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.down\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.health.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.health.last\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.client.aborted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.component_type\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.compressor.bypassed.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.compressor.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.compressor.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.compressor.response.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.connection.retried\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.connection.time.avg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.connection.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.downtime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.last_change\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.proxy.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.proxy.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.queue.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.queue.time.avg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.connection.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.denied\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.queued.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.queued.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.redispatched\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.denied\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.1xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.2xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.3xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.4xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.5xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.other\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.time.avg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.selected.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.server.aborted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.server.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.server.backup\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.server.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.service_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.rate.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.throttle.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.tracked.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.weight\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.containerized\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.build\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.codename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.content\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.referrer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.content\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.phrase\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.status_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.agent.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.secured\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.server.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.server.vendor\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.server.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.url\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.broker.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.broker.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.broker.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.broker.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.client.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.client.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.client.member_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.error.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.meta\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.partition\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.topic\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.broker.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.broker.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.offset.newest\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.offset.oldest\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.error.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.insync_replica\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.is_leader\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.isr\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.leader\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.replica\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.topic.error.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.topic.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.topic_broker_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.topic_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.topic.error.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.topic.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.concurrent_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.host.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.index\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.process.event_loop_delay.ms\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.process.memory.heap.size_limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.process.memory.heap.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.process.memory.heap.uptime.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.process.memory.heap.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.request.disconnects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.request.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.response_time.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.response_time.max.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.snapshot\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.status.metrics.concurrent_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.status.metrics.requests.disconnects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.status.metrics.requests.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.status.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.status.status.overall.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.audit.event.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.audit.rejected.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.client.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.etcd.object.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.request.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.request.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.request.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.request.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.response.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.response.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.process.cpu.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.process.fds.open.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.process.memory.resident.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.process.memory.virtual.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.process.started.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.client\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.component\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.content_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.current.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.dry_run\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.group\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.handler\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.kind\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.latency.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.latency.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.longrunning.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.resource\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.scope\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.subresource\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.verb\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.limit.cores\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.limit.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.request.cores\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.request.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.usage.core.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.usage.limit.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.usage.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.usage.node.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.image\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.inodes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.inodes.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.inodes.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.majorpagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.pagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.request.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.usage.limit.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.usage.node.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.workingset.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.rootfs.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.rootfs.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.rootfs.inodes.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.rootfs.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.status.phase\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.status.ready\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.status.reason\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.status.restarts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.client.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.handler\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.request.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.request.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.request.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.request.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.response.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.response.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.leader.is_master\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.node.collector.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.node.collector.eviction.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.node.collector.health.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.node.collector.unhealthy.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.process.cpu.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.process.fds.open.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.process.memory.resident.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.process.memory.virtual.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.process.started.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.workqueue.adds.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.workqueue.depth.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.workqueue.longestrunning.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.workqueue.retries.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.workqueue.unfinished.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.active.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.concurrency\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.created.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.deadline.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.is_suspended\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.last_schedule.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.next_schedule.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.schedule\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.paused\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.replicas.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.replicas.desired\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.replicas.unavailable\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.replicas.updated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.involved_object.api_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.involved_object.kind\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.involved_object.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.involved_object.resource_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.involved_object.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.message\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.namespace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.resource_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.self_link\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.timestamp.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.reason\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.timestamp.first_occurrence\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.timestamp.last_occurrence\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.namespace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.cpu.allocatable.cores\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.cpu.capacity.cores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.cpu.usage.core.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.cpu.usage.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.inodes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.inodes.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.inodes.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.allocatable.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.majorpagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.pagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.workingset.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.network.rx.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.network.rx.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.network.tx.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.network.tx.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.pod.allocatable.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.pod.capacity.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.runtime.imagefs.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.runtime.imagefs.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.runtime.imagefs.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.status.ready\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.status.unschedulable\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.cpu.usage.limit.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.cpu.usage.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.cpu.usage.node.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.host_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.major_page_faults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.page_faults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.usage.limit.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.usage.node.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.working_set.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.network.rx.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.network.rx.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.network.tx.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.network.tx.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.status.phase\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.status.ready\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.status.scheduled\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.client.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.handler\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.request.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.request.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.request.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.request.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.response.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.response.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.process.cpu.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.process.fds.open.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.process.memory.resident.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.process.memory.virtual.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.process.started.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.sync.networkprogramming.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.sync.networkprogramming.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.sync.rules.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.sync.rules.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.replicas.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.replicas.desired\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.replicas.labeled\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.replicas.observed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.replicas.ready\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.client.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.handler\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.request.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.request.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.request.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.request.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.response.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.response.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.leader.is_master\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.operation\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.process.cpu.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.process.fds.open.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.process.memory.resident.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.process.memory.virtual.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.process.started.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.result\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.duration.seconds.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.duration.seconds.sum\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.e2e.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.e2e.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.pod.attempts.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.pod.preemption.victims.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.created\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.generation.desired\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.generation.observed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.replicas.desired\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.replicas.observed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.container\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.cpu.usage.core.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.cpu.usage.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.memory.majorpagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.memory.pagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.memory.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.memory.workingset.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.inodes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.inodes.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.inodes.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kvm.dommemstat.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kvm.dommemstat.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kvm.dommemstat.stat.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kvm.dommemstat.stat.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.level\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.logger\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"logstash.node.jvm.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"logstash.node.stats.events.filtered\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"logstash.node.stats.events.in\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"logstash.node.stats.events.out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.bytes.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.bytes.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.cmd.get\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.cmd.set\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.connections.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.connections.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.evictions\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.get.hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.get.misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.items.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.items.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.threads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.uptime.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.written.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"metricset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"metricset.period\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.collection\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.commands.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.commands.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.db\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.getmore.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.getmore.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.insert.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.insert.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.lock.read.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.lock.read.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.lock.write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.lock.write.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.queries.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.queries.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.remove.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.remove.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.total.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.total.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.update.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.update.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.avg_obj_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.collections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.data_file_version.major\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.data_file_version.minor\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.data_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.db\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.extent_free_list.num\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.extent_free_list.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.file_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.index_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.indexes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.ns_size_mb.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.num_extents\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.storage_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.aggregate.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.aggregate.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.build_info.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.build_info.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.coll_stats.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.coll_stats.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.connection_pool_stats.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.connection_pool_stats.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.count.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.count.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.db_stats.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.db_stats.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.distinct.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.distinct.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.find.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.find.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_cmd_line_opts.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_cmd_line_opts.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_last_error.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_last_error.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_log.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_log.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_more.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_more.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_parameter.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_parameter.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.host_info.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.host_info.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.insert.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.insert.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.is_master.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.is_master.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.is_self.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.is_self.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.last_collections.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.last_collections.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.last_commands.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.last_commands.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.list_databased.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.list_databased.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.list_indexes.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.list_indexes.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.ping.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.ping.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.profile.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.profile.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_get_rbid.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_get_rbid.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_get_status.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_get_status.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_heartbeat.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_heartbeat.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_update_position.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_update_position.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.server_status.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.server_status.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.update.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.update.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.whatsmyuri.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.whatsmyuri.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.cursor.open.no_timeout\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.cursor.open.pinned\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.cursor.open.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.cursor.timed_out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.document.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.document.inserted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.document.returned\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.document.updated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.get_last_error.write_timeouts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.get_last_error.write_wait.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.get_last_error.write_wait.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.operation.scan_and_order\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.operation.write_conflicts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.query_executor.scanned_documents\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.query_executor.scanned_indexes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.apply.attempts_to_become_secondary\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.apply.batches.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.apply.batches.time.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.apply.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.buffer.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.buffer.max_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.buffer.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.cancels\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.event_created\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.event_wait\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.dbwork\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.exclusive\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.netcmd\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.work\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.work_at\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.waits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.event_waiters\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.network_interface\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.in_progress.dbwork\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.in_progress.exclusive\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.in_progress.network\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.ready\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.sleepers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.shutting_down\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.unsignaled_events\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.initial_sync.completed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.initial_sync.failed_attempts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.initial_sync.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.network.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.network.getmores.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.network.getmores.time.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.network.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.network.reders_created\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.preload.docs.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.preload.docs.time.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.preload.indexes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.preload.indexes.time.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.storage.free_list.search.bucket_exhausted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.storage.free_list.search.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.storage.free_list.search.scanned\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.ttl.deleted_documents\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.ttl.passes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.headroom.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.headroom.min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.lag.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.lag.min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.arbiter.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.arbiter.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.down.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.down.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.primary.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.primary.optime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.recovering.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.recovering.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.rollback.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.rollback.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.secondary.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.secondary.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.secondary.optimes\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.startup2.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.startup2.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.unhealthy.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.unhealthy.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.unknown.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.unknown.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.oplog.first.timestamp\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.oplog.last.timestamp\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.oplog.size.allocated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.oplog.size.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.oplog.window\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.optimes.applied\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.optimes.durable\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.optimes.last_committed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.server_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.set_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.asserts.msg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.asserts.regular\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.asserts.rollovers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.asserts.user\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.asserts.warning\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.background_flushing.average.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.background_flushing.flushes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.background_flushing.last.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.background_flushing.last_finished\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.background_flushing.total.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.connections.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.connections.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.connections.total_created\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.extra_info.heap_usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.extra_info.page_faults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.active_clients.readers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.active_clients.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.active_clients.writers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.current_queue.readers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.current_queue.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.current_queue.writers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.total_time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.commits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.commits_in_write_lock\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.compression\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.early_commits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.journaled.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.commits.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.commits_in_write_lock.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.dt.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.prep_log_buffer.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.remap_private_view.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.write_to_data_files.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.write_to_journal.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.write_to_data_files.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.local_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.acquire.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.acquire.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.acquire.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.acquire.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.deadlock.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.deadlock.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.deadlock.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.deadlock.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.us.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.us.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.us.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.us.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.acquire.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.acquire.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.acquire.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.acquire.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.deadlock.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.deadlock.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.deadlock.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.deadlock.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.us.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.us.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.us.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.us.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.acquire.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.acquire.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.acquire.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.acquire.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.deadlock.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.deadlock.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.deadlock.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.deadlock.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.us.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.us.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.us.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.us.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.acquire.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.acquire.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.acquire.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.acquire.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.deadlock.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.deadlock.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.deadlock.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.deadlock.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.us.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.us.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.us.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.us.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.acquire.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.acquire.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.acquire.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.acquire.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.deadlock.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.deadlock.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.deadlock.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.deadlock.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.us.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.us.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.us.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.us.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.memory.bits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.memory.mapped.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.memory.mapped_with_journal.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.memory.resident.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.memory.virtual.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.network.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.network.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.network.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.command\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.delete\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.getmore\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.insert\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.query\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.update\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.commands.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.commands.latency\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.reads.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.reads.latency\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.writes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.writes.latency\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.command\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.delete\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.getmore\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.insert\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.query\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.update\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.process\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.storage_engine.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.uptime.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.dirty.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.maximum.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.pages.evicted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.pages.read\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.pages.write\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.read.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.read.out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.read.total_tickets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.write.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.write.out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.write.total_tickets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.flushes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.max_file_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.scans\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.syncs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.writes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.write_backs_queued\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.database.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.database.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.active_temp_tables\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.batch_requests_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.buffer.cache_hit.pct\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.buffer.checkpoint_pages_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.buffer.database_pages\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.buffer.page_life_expectancy.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.buffer.target_pages\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.compilations_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.connections_reset_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.lock_waits_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.logins_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.logouts_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.page_splits_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.recompilations_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.transactions\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.user_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.space_usage.since_last_backup.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.space_usage.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.space_usage.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.space_usage.used.pct\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.stats.active_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.stats.backup_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.stats.recovery_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.stats.since_last_checkpoint.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.stats.total_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"munin.plugin.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.apply.oooe\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.apply.oool\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.apply.window\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cert.deps_distance\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cert.index_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cert.interval\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cluster.conf_id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cluster.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cluster.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.commit.oooe\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.commit.window\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.connected\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.evs.evict\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.evs.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.flow_ctl.paused\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.flow_ctl.paused_ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.flow_ctl.recv\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.flow_ctl.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.last_committed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.bf_aborts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.cert_failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.commits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.recv.queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.recv.queue_avg\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.recv.queue_max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.recv.queue_min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.replays\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.send.queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.send.queue_avg\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.send.queue_max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.send.queue_min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.ready\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.received.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.received.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.data_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.keys\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.keys_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.other_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.aborted.clients\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.aborted.connects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.binlog.cache.disk_use\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.binlog.cache.use\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.bytes.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.bytes.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.command.delete\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.command.insert\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.command.select\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.command.update\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.created.tmp.disk_tables\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.created.tmp.files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.created.tmp.tables\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.delayed.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.delayed.insert_threads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.delayed.writes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.flush_commands\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.commit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.delete\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.external_lock\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.mrr_init\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.prepare\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.first\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.key\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.last\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.next\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.prev\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.rnd\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.rnd_next\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.rollback\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.savepoint\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.savepoint_rollback\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.update\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.write\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.bytes.data\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.bytes.dirty\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.dump_status\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.load_status\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.data\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.dirty\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.flushed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.latched\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.misc\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pool.reads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pool.resize_status\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pool.wait_free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.read.ahead\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.read.ahead_evicted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.read.ahead_rnd\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.read.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.write_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.max_used_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.open.files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.open.streams\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.open.tables\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.opened_tables\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.queries\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.questions\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.threads.cached\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.threads.connected\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.threads.created\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.threads.running\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.connections.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.routes.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.server.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.server.time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.cores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.cpu\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.http.req_stats.uri.connz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.http.req_stats.uri.root\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.http.req_stats.uri.routez\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.http.req_stats.uri.subsz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.http.req_stats.uri.varz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.in.messages\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.mem.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.out.messages\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.remotes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.slow_consumers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.total_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.cache.fanout.avg\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.cache.fanout.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.cache.hit_rate\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.cache.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.inserts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.matches\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.removes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.application\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.community_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.direction\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.forwarded_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.iana_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.transport\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.accepts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.handled\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.reading\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.waiting\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.writing\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.vendor\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.buffer_pool\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cache.buffer.hit.pct\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cache.get.consistent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cache.get.db_blocks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cache.physical_reads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.avg\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.cache_hit.pct\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.max\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.opened.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.opened.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.parse.real\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.parse.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.session.cache_hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.total\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.io_reloads\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.lock_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.machine\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.pin_requests\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.username\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.online_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.size.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.size.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.space.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.space.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.space.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.connections.accepted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.connections.listen_queue_len\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.connections.max_listen_queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.connections.queued\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.process_manager\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.processes.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.processes.idle\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.processes.max_active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.processes.max_children_reached\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.processes.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.slow_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.start_since\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.last_request_cpu\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.last_request_memory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.request_duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.script\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.start_since\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.application_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.backend_start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.client.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.client.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.client.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.database.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.database.oid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.query\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.query_start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.state_change\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.transaction_start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.user.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.waiting\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.allocated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.backend\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.backend_fsync\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.checkpoints\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.clean\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.clean_full\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.checkpoints.requested\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.checkpoints.scheduled\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.checkpoints.times.sync.ms\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.checkpoints.times.write.ms\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.stats_reset\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.blocks.hit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.blocks.read\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.blocks.time.read.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.blocks.time.write.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.conflicts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.deadlocks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.number_of_backends\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.oid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.rows.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.rows.fetched\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.rows.inserted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.rows.returned\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.rows.updated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.stats_reset\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.temporary.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.temporary.files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.transactions.commit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.transactions.rollback\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.database.oid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.calls\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.local.dirtied\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.local.hit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.local.read\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.local.written\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.shared.dirtied\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.shared.hit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.shared.read\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.shared.written\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.temp.read\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.temp.written\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.rows\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.text\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.time.max.ms\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.time.mean.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.time.min.ms\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.time.stddev.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.time.total.ms\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.user.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.args\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.executable\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pgid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.ppid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.thread.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.thread.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.title\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.working_directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.channel_max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.channels\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.client_provided.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.frame_max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.octet_count.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.octet_count.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.packet_count.pending\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.packet_count.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.packet_count.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.peer.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.peer.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.auto_delete\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.durable\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.internal\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.messages.publish_in.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.messages.publish_in.details.rate\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.messages.publish_out.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.messages.publish_out.details.rate\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.disk.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.disk.free.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.fd.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.fd.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.gc.num.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.gc.reclaimed.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.file_handle.open_attempt.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.file_handle.open_attempt.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.read.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.read.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.reopen.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.seek.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.seek.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.sync.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.sync.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.write.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.mem.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.mem.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.mnesia.disk.tx.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.mnesia.ram.tx.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.msg.store_read.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.msg.store_write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.proc.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.proc.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.processors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.queue.index.journal_write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.queue.index.read.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.queue.index.write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.run.queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.socket.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.socket.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.arguments.max_priority\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.auto_delete\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.consumers.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.consumers.utilisation.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.disk.reads.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.disk.writes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.durable\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.exclusive\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.persistent.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.ready.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.ready.details.rate\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.total.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.total.details.rate\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.unacknowledged.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.unacknowledged.details.rate\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.vhost\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.biggest_input_buf\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.blocked\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.connected\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.longest_output_list\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.max_input_buffer\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.max_output_buffer\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.cluster.enabled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.cpu.used.sys\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.cpu.used.sys_children\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.cpu.used.user\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.cpu.used.user_children\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.active_defrag.is_running\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.allocated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.fragmentation.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.fragmentation.ratio\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.resident\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.rss.ratio\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.fragmentation.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.fragmentation.ratio\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.max.policy\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.max.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.used.dataset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.used.lua\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.used.peak\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.used.rss\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.used.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.bgrewrite.last_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.buffer.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.copy_on_write.last_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.enabled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.fsync.delayed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.fsync.pending\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.rewrite.buffer.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.rewrite.current_time.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.rewrite.in_progress\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.rewrite.last_time.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.rewrite.scheduled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.size.base\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.size.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.write.last_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.loading\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.bgsave.current_time.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.bgsave.in_progress\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.bgsave.last_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.bgsave.last_time.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.copy_on_write.last_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.last_save.changes_since\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.last_save.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.backlog.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.backlog.first_byte_offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.backlog.histlen\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.backlog.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.connected_slaves\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.last_io_seconds_ago\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.link_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.second_offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.sync.in_progress\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.sync.last_io_seconds_ago\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.sync.left_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master_offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.role\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.slave.is_readonly\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.slave.offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.slave.priority\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.arch_bits\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.build_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.config_file\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.gcc_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.git_dirty\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.git_sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.hz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.lru_clock\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.mode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.multiplexing_api\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.run_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.tcp_port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.slowlog.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.active_defrag.hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.active_defrag.key_hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.active_defrag.key_misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.active_defrag.misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.commands_processed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.connections.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.connections.rejected\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.instantaneous.input_kbps\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.instantaneous.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.instantaneous.output_kbps\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.keys.evicted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.keys.expired\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.keyspace.hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.keyspace.misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.latest_fork_usec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.migrate_cached_sockets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.net.input.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.net.output.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.pubsub.channels\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.pubsub.patterns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.slave_expires_tracked_keys\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.sync.full\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.sync.partial.err\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.sync.partial.ok\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.key.expire.ttl\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.key.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.key.length\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.key.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.key.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.keyspace.avg_ttl\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.keyspace.expires\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.keyspace.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.keyspace.keys\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.ephemeral_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.idle.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.idle.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.iowait.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.iowait.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.irq.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.irq.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.nice.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.nice.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.softirq.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.softirq.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.steal.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.steal.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.system.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.system.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.user.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.user.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.cores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.idle.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.idle.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.idle.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.iowait.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.iowait.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.iowait.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.irq.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.irq.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.irq.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.nice.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.nice.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.nice.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.softirq.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.softirq.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.softirq.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.steal.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.steal.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.steal.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.system.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.system.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.system.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.total.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.total.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":3,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.user.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.user.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.user.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.io.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.await\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.busy\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.queue.avg_size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.read.await\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.read.per_sec.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.read.request.merges_per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.read.request.per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.request.avg_size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.service_time\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.write.await\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.write.per_sec.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.write.request.merges_per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.write.request.per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.read.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.read.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.write.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.entropy.available_bits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.entropy.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.device_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.free_files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.mount_point\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.used.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.fsstat.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.fsstat.total_files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.fsstat.total_size.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.fsstat.total_size.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.fsstat.total_size.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.1\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.15\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.5\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.cores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.norm.1\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.norm.15\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.norm.5\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.actual.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.actual.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.actual.used.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.default_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.reserved\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.surplus\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.used.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.swap.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.swap.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.swap.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.swap.used.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.used.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.in.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.in.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.in.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.out.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.out.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.out.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.blkio.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.blkio.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.blkio.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.blkio.total.ios\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.cfs.period.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.cfs.quota.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.cfs.shares\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.rt.period.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.rt.runtime.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.stats.periods\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.stats.throttled.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.stats.throttled.periods\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpuacct.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpuacct.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpuacct.stats.system.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpuacct.stats.user.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpuacct.total.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem.usage.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem_tcp.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem_tcp.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem_tcp.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem_tcp.usage.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.usage.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.memsw.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.memsw.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.memsw.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.memsw.usage.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.active_anon.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.active_file.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.cache.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.hierarchical_memory_limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.hierarchical_memsw_limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.inactive_anon.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.inactive_file.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.major_page_faults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.mapped_file.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.page_faults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.pages_in\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.pages_out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.rss_huge.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.swap.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.unevictable.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cmdline\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.system.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.user.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.fd.limit.hard\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.fd.limit.soft\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.fd.open\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.rss.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.share\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.dead\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.idle\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.running\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.sleeping\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.stopped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.unknown\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.zombie\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.blocks.synced\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.blocks.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.disks.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.disks.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.disks.spare\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.disks.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.level\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.sync_action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.local.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.local.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.process.cmdline\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.remote.etld_plus_one\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.remote.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.remote.host_error\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.remote.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.remote.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.all.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.all.listening\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.close_wait\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.established\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.listening\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.orphan\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.time_wait\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.memory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.udp.all.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.udp.memory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.uptime.duration.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timeseries.instance\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tracing.trace.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tracing.transaction.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"traefik.health.response.avg_time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"traefik.health.response.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"traefik.health.uptime.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.fragment\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.password\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.query\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.scheme\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.username\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.device.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.read_errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.requests.offloaded\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.requests.routed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.requests.static\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.requests.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.worker_pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.write_errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.total.exceptions\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.total.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.total.read_errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.total.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.total.write_errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.accepting\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.avg_rt\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.delta_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.exceptions\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.harakiri_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.respawn_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.rss\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.running_time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.signal_queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.signals\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.tx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.vsz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.capacity.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.capacity.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.capacity.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.capacity.used.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.fstype\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.cpu.free.mhz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.cpu.total.mhz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.cpu.used.mhz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.memory.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.memory.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.memory.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.network_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.cpu.used.mhz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.memory.free.guest.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.memory.total.guest.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.memory.used.guest.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.memory.used.host.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.network_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.os\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.display_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.exit_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.path_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.start_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.start_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.uptime.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.connection.interest_ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.connection.queued\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.connection.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.connection.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.approximate_data_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.ephemerals_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.followers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.latency.avg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.latency.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.latency.min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.max_file_descriptor_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.num_alive_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.open_file_descriptor_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.outstanding_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.packets.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.packets.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.pending_syncs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.server_state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.synced_followers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.watch_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.znode_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.epoch\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.latency.avg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.latency.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.latency.min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.mode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.node_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.outstanding\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.version_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.zxid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", - "timeFieldName" : "@timestamp", - "title" : "metricbeat-*" - }, - "type" : "index-pattern", - "migrationVersion" : { - "index-pattern" : "7.6.0" - }, - "updated_at" : "2020-01-22T15:34:59.061Z" + "id": "index-pattern:metricbeat-*", + "index": ".kibana_1", + "source": { + "index-pattern": { + "fieldFormatMap": "{\"aerospike.namespace.device.available.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.device.free.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.device.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.device.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.free.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.memory.used.data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.index.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.sindex.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"aws.rds.disk_usage.bin_log.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.free_local_storage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.free_storage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.freeable_memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.latency.commit\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.ddl\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.dml\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.insert\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.read\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.select\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.update\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.write\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.replica_lag.sec\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.swap_usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.volume_used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_daily_storage.bucket.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.downloaded.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.latency.first_byte.ms\":{\"id\":\"duration\",\"params\":{}},\"aws.s3_request.latency.total_request.ms\":{\"id\":\"duration\",\"params\":{}},\"aws.s3_request.requests.select_returned.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.requests.select_scanned.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.uploaded.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.sqs.oldest_message_age.sec\":{\"id\":\"duration\",\"params\":{}},\"aws.sqs.sent_message_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.degraded.ratio\":{\"id\":\"percent\",\"params\":{}},\"ceph.cluster_status.misplace.ratio\":{\"id\":\"percent\",\"params\":{}},\"ceph.cluster_status.pg.avail_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.data_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.total_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.used_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.traffic.read_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.traffic.write_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.log.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.misc.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.sst.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.total.byte\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.used.byte\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.used.pct\":{\"id\":\"percent\",\"params\":{}},\"ceph.pool_disk.stats.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.pool_disk.stats.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"client.bytes\":{\"id\":\"bytes\",\"params\":{}},\"client.nat.port\":{\"id\":\"string\",\"params\":{}},\"client.port\":{\"id\":\"string\",\"params\":{}},\"coredns.stats.dns.request.duration.ns.sum\":{\"id\":\"duration\",\"params\":{}},\"couchbase.bucket.data.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.disk.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.quota.ram.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.quota.use.pct\":{\"id\":\"percent\",\"params\":{}},\"couchbase.cluster.hdd.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.quota.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.used.by_data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.total.per_node.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.total.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.used.per_node.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.used.by_data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.couch.docs.data_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.couch.docs.disk_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.mcd_memory.allocated.bytes\":{\"id\":\"bytes\",\"params\":{}},\"destination.bytes\":{\"id\":\"bytes\",\"params\":{}},\"destination.nat.port\":{\"id\":\"string\",\"params\":{}},\"destination.port\":{\"id\":\"string\",\"params\":{}},\"docker.cpu.core.*.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.kernel.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.system.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.user.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.diskio.summary.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.commit.peak\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.commit.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.limit\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.private_working_set.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.rss.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.memory.rss.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.usage.max\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.usage.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.memory.usage.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.inbound.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.outbound.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.primaries.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.primaries.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.total.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.total.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.total.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.total.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.heap.init.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.heap.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.nonheap.init.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.nonheap.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.indices.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.disk.mvcc_db_total_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.memory.go_memstats_alloc.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.network.client_grpc_received.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.network.client_grpc_sent.bytes\":{\"id\":\"bytes\",\"params\":{}},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\",\"params\":{}},\"event.severity\":{\"id\":\"string\",\"params\":{}},\"golang.heap.allocations.active\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.allocated\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.idle\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.total\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.gc.next_gc_limit\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.obtained\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.released\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.stack\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.total\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.info.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"haproxy.info.memory.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.info.ssl.frontend.session_reuse.pct\":{\"id\":\"percent\",\"params\":{}},\"haproxy.stat.compressor.bypassed.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.response.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.throttle.pct\":{\"id\":\"percent\",\"params\":{}},\"http.request.body.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.request.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.body.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.status_code\":{\"id\":\"string\",\"params\":{}},\"kibana.stats.process.memory.heap.size_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kibana.stats.process.memory.heap.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kibana.stats.process.memory.heap.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.cpu.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.cpu.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.logs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.logs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.logs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.request.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.memory.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.allocatable.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.network.rx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.network.tx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.cpu.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.cpu.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.working_set.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.network.rx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.network.tx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.avg_obj_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.data_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.extent_free_list.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.file_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.index_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.storage_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.replstatus.headroom.max\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.headroom.min\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.lag.max\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.lag.min\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.oplog.size.allocated\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.replstatus.oplog.size.used\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.extra_info.heap_usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.dirty.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.maximum.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.max_file_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mysql.status.bytes.received\":{\"id\":\"bytes\",\"params\":{}},\"mysql.status.bytes.sent\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.cpu\":{\"id\":\"percent\",\"params\":{}},\"nats.stats.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.mem.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.uptime\":{\"id\":\"duration\",\"params\":{}},\"nats.subscriptions.cache.hit_rate\":{\"id\":\"percent\",\"params\":{}},\"network.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"process.pgid\":{\"id\":\"string\",\"params\":{}},\"process.pid\":{\"id\":\"string\",\"params\":{}},\"process.ppid\":{\"id\":\"string\",\"params\":{}},\"process.thread.id\":{\"id\":\"string\",\"params\":{}},\"rabbitmq.connection.frame_max\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.disk.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.disk.free.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.gc.reclaimed.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.io.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.io.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.mem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.queue.consumers.utilisation.pct\":{\"id\":\"percent\",\"params\":{}},\"rabbitmq.queue.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.active\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.allocated\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.fragmentation.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.resident\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.fragmentation.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.max.value\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.dataset\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.lua\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.peak\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.rss\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.value\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.buffer.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.copy_on_write.last_size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.rewrite.buffer.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.rewrite.current_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.aof.rewrite.last_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.aof.size.base\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.size.current\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.rdb.bgsave.current_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.rdb.bgsave.last_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.rdb.copy_on_write.last_size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.replication.backlog.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.replication.master.last_io_seconds_ago\":{\"id\":\"duration\",\"params\":{}},\"redis.info.replication.master.sync.last_io_seconds_ago\":{\"id\":\"duration\",\"params\":{}},\"redis.info.replication.master.sync.left_bytes\":{\"id\":\"bytes\",\"params\":{}},\"server.bytes\":{\"id\":\"bytes\",\"params\":{}},\"server.nat.port\":{\"id\":\"string\",\"params\":{}},\"server.port\":{\"id\":\"string\",\"params\":{}},\"source.bytes\":{\"id\":\"bytes\",\"params\":{}},\"source.nat.port\":{\"id\":\"string\",\"params\":{}},\"source.port\":{\"id\":\"string\",\"params\":{}},\"system.core.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.iowait.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.irq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.nice.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.softirq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.steal.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.system.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.user.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.idle.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.iowait.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.iowait.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.irq.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.irq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.nice.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.nice.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.softirq.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.softirq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.steal.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.steal.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.system.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.system.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.total.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.user.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.user.pct\":{\"id\":\"percent\",\"params\":{}},\"system.diskio.iostat.read.per_sec.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.iostat.write.per_sec.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.entropy.pct\":{\"id\":\"percent\",\"params\":{}},\"system.filesystem.available\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.free\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.total\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.fsstat.total_size.free\":{\"id\":\"bytes\",\"params\":{}},\"system.fsstat.total_size.total\":{\"id\":\"bytes\",\"params\":{}},\"system.fsstat.total_size.used\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.default_size\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.free\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.reserved\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.surplus\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.total\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.swap.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.total\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.total\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.blkio.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.active_anon.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.active_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.cache.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.hierarchical_memory_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.hierarchical_memsw_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.inactive_anon.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.inactive_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.mapped_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.rss_huge.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.swap.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.unevictable.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.memory.rss.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.memory.share\":{\"id\":\"bytes\",\"params\":{}},\"system.process.memory.size\":{\"id\":\"bytes\",\"params\":{}},\"system.socket.summary.tcp.memory\":{\"id\":\"bytes\",\"params\":{}},\"system.socket.summary.udp.memory\":{\"id\":\"bytes\",\"params\":{}},\"system.uptime.duration.ms\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\"}},\"url.port\":{\"id\":\"string\",\"params\":{}},\"vsphere.datastore.capacity.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.used.pct\":{\"id\":\"percent\",\"params\":{}},\"vsphere.host.memory.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.host.memory.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.host.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.free.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.total.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.used.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.used.host.bytes\":{\"id\":\"bytes\",\"params\":{}},\"windows.service.uptime.ms\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\"}}}", + "timeFieldName": "@timestamp", + "title": "metricbeat-*" + }, + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [ + ], + "type": "index-pattern", + "updated_at": "2020-01-22T15:34:59.061Z" } } } @@ -122,22 +125,19 @@ { "type": "doc", "value": { - "type": "_doc", - "index" : ".kibana", - "type": "doc", - "id" : "custom-space:index-pattern:metricbeat-*", - "source" : { - "index-pattern" : { - "fieldFormatMap" : "{\"aerospike.namespace.device.available.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.device.free.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.device.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.device.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.free.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.memory.used.data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.index.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.sindex.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"aws.rds.disk_usage.bin_log.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.free_local_storage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.free_storage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.freeable_memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.latency.commit\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.ddl\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.dml\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.insert\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.read\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.select\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.update\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.write\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.replica_lag.sec\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.swap_usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.volume_used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_daily_storage.bucket.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.downloaded.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.latency.first_byte.ms\":{\"id\":\"duration\",\"params\":{}},\"aws.s3_request.latency.total_request.ms\":{\"id\":\"duration\",\"params\":{}},\"aws.s3_request.requests.select_returned.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.requests.select_scanned.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.uploaded.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.sqs.oldest_message_age.sec\":{\"id\":\"duration\",\"params\":{}},\"aws.sqs.sent_message_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.degraded.ratio\":{\"id\":\"percent\",\"params\":{}},\"ceph.cluster_status.misplace.ratio\":{\"id\":\"percent\",\"params\":{}},\"ceph.cluster_status.pg.avail_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.data_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.total_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.used_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.traffic.read_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.traffic.write_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.log.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.misc.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.sst.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.total.byte\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.used.byte\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.used.pct\":{\"id\":\"percent\",\"params\":{}},\"ceph.pool_disk.stats.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.pool_disk.stats.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"client.bytes\":{\"id\":\"bytes\",\"params\":{}},\"client.nat.port\":{\"id\":\"string\",\"params\":{}},\"client.port\":{\"id\":\"string\",\"params\":{}},\"coredns.stats.dns.request.duration.ns.sum\":{\"id\":\"duration\",\"params\":{}},\"couchbase.bucket.data.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.disk.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.quota.ram.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.quota.use.pct\":{\"id\":\"percent\",\"params\":{}},\"couchbase.cluster.hdd.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.quota.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.used.by_data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.total.per_node.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.total.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.used.per_node.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.used.by_data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.couch.docs.data_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.couch.docs.disk_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.mcd_memory.allocated.bytes\":{\"id\":\"bytes\",\"params\":{}},\"destination.bytes\":{\"id\":\"bytes\",\"params\":{}},\"destination.nat.port\":{\"id\":\"string\",\"params\":{}},\"destination.port\":{\"id\":\"string\",\"params\":{}},\"docker.cpu.core.*.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.kernel.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.system.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.user.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.diskio.summary.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.commit.peak\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.commit.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.limit\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.private_working_set.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.rss.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.memory.rss.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.usage.max\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.usage.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.memory.usage.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.inbound.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.outbound.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.primaries.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.primaries.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.total.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.total.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.total.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.total.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.heap.init.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.heap.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.nonheap.init.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.nonheap.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.indices.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.disk.mvcc_db_total_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.memory.go_memstats_alloc.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.network.client_grpc_received.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.network.client_grpc_sent.bytes\":{\"id\":\"bytes\",\"params\":{}},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\",\"params\":{}},\"event.severity\":{\"id\":\"string\",\"params\":{}},\"golang.heap.allocations.active\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.allocated\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.idle\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.total\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.gc.next_gc_limit\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.obtained\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.released\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.stack\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.total\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.info.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"haproxy.info.memory.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.info.ssl.frontend.session_reuse.pct\":{\"id\":\"percent\",\"params\":{}},\"haproxy.stat.compressor.bypassed.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.response.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.throttle.pct\":{\"id\":\"percent\",\"params\":{}},\"http.request.body.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.request.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.body.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.status_code\":{\"id\":\"string\",\"params\":{}},\"kibana.stats.process.memory.heap.size_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kibana.stats.process.memory.heap.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kibana.stats.process.memory.heap.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.cpu.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.cpu.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.logs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.logs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.logs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.request.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.memory.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.allocatable.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.network.rx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.network.tx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.cpu.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.cpu.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.working_set.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.network.rx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.network.tx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.avg_obj_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.data_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.extent_free_list.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.file_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.index_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.storage_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.replstatus.headroom.max\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.headroom.min\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.lag.max\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.lag.min\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.oplog.size.allocated\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.replstatus.oplog.size.used\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.extra_info.heap_usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.dirty.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.maximum.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.max_file_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mysql.status.bytes.received\":{\"id\":\"bytes\",\"params\":{}},\"mysql.status.bytes.sent\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.cpu\":{\"id\":\"percent\",\"params\":{}},\"nats.stats.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.mem.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.uptime\":{\"id\":\"duration\",\"params\":{}},\"nats.subscriptions.cache.hit_rate\":{\"id\":\"percent\",\"params\":{}},\"network.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"process.pgid\":{\"id\":\"string\",\"params\":{}},\"process.pid\":{\"id\":\"string\",\"params\":{}},\"process.ppid\":{\"id\":\"string\",\"params\":{}},\"process.thread.id\":{\"id\":\"string\",\"params\":{}},\"rabbitmq.connection.frame_max\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.disk.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.disk.free.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.gc.reclaimed.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.io.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.io.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.mem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.queue.consumers.utilisation.pct\":{\"id\":\"percent\",\"params\":{}},\"rabbitmq.queue.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.active\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.allocated\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.fragmentation.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.resident\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.fragmentation.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.max.value\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.dataset\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.lua\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.peak\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.rss\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.value\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.buffer.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.copy_on_write.last_size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.rewrite.buffer.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.rewrite.current_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.aof.rewrite.last_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.aof.size.base\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.size.current\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.rdb.bgsave.current_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.rdb.bgsave.last_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.rdb.copy_on_write.last_size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.replication.backlog.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.replication.master.last_io_seconds_ago\":{\"id\":\"duration\",\"params\":{}},\"redis.info.replication.master.sync.last_io_seconds_ago\":{\"id\":\"duration\",\"params\":{}},\"redis.info.replication.master.sync.left_bytes\":{\"id\":\"bytes\",\"params\":{}},\"server.bytes\":{\"id\":\"bytes\",\"params\":{}},\"server.nat.port\":{\"id\":\"string\",\"params\":{}},\"server.port\":{\"id\":\"string\",\"params\":{}},\"source.bytes\":{\"id\":\"bytes\",\"params\":{}},\"source.nat.port\":{\"id\":\"string\",\"params\":{}},\"source.port\":{\"id\":\"string\",\"params\":{}},\"system.core.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.iowait.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.irq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.nice.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.softirq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.steal.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.system.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.user.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.idle.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.iowait.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.iowait.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.irq.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.irq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.nice.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.nice.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.softirq.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.softirq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.steal.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.steal.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.system.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.system.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.total.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.user.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.user.pct\":{\"id\":\"percent\",\"params\":{}},\"system.diskio.iostat.read.per_sec.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.iostat.write.per_sec.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.entropy.pct\":{\"id\":\"percent\",\"params\":{}},\"system.filesystem.available\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.free\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.total\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.fsstat.total_size.free\":{\"id\":\"bytes\",\"params\":{}},\"system.fsstat.total_size.total\":{\"id\":\"bytes\",\"params\":{}},\"system.fsstat.total_size.used\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.default_size\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.free\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.reserved\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.surplus\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.total\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.swap.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.total\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.total\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.blkio.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.active_anon.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.active_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.cache.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.hierarchical_memory_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.hierarchical_memsw_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.inactive_anon.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.inactive_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.mapped_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.rss_huge.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.swap.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.unevictable.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.memory.rss.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.memory.share\":{\"id\":\"bytes\",\"params\":{}},\"system.process.memory.size\":{\"id\":\"bytes\",\"params\":{}},\"system.socket.summary.tcp.memory\":{\"id\":\"bytes\",\"params\":{}},\"system.socket.summary.udp.memory\":{\"id\":\"bytes\",\"params\":{}},\"system.uptime.duration.ms\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\"}},\"url.port\":{\"id\":\"string\",\"params\":{}},\"vsphere.datastore.capacity.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.used.pct\":{\"id\":\"percent\",\"params\":{}},\"vsphere.host.memory.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.host.memory.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.host.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.free.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.total.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.used.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.used.host.bytes\":{\"id\":\"bytes\",\"params\":{}},\"windows.service.uptime.ms\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\"}}}", - "fields" : "[{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"aerospike.namespace.client.delete.error\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.delete.not_found\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.delete.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.delete.timeout\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.read.error\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.read.not_found\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.read.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.read.timeout\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.write.error\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.write.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.client.write.timeout\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.device.available.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.device.free.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.device.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.device.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.hwm_breached\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.memory.free.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.memory.used.data.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.memory.used.index.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.memory.used.sindex.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.memory.used.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.node.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.objects.master\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.objects.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aerospike.namespace.stop_writes\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.ephemeral_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.bytes_per_request\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.bytes_per_sec\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.connections.async.closing\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.connections.async.keep_alive\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.connections.async.writing\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.connections.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.cpu.children_system\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.cpu.children_user\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.cpu.load\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.cpu.system\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.cpu.user\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.load.1\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.load.15\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.load.5\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.requests_per_sec\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.closing_connection\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.dns_lookup\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.gracefully_finishing\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.idle_cleanup\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.keepalive\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.logging\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.open_slot\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.reading_request\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.sending_reply\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.starting_up\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.scoreboard.waiting_for_connection\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.total_accesses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.total_kbytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.uptime.server_uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.uptime.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.workers.busy\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"apache.status.workers.idle\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.cloudwatch.namespace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.cpu.credit_balance\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.cpu.credit_usage\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.cpu.surplus_credit_balance\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.cpu.surplus_credits_charged\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.cpu.total.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.read.bytes_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.read.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.read.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.write.bytes_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.write.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.diskio.write.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.core.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.image.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.monitoring.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.private.dns_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.private.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.public.dns_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.public.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.state.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.state.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.instance.threads_per_core\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.in.bytes_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.in.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.in.packets_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.out.bytes_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.out.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.network.out.packets_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.status.check_failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.status.check_failed_instance\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.ec2.status.check_failed_system\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.cpu.credit_balance\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.cpu.credit_usage\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.cpu.total.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.database_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.db_instance.arn\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.db_instance.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.db_instance.identifier\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.db_instance.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.deadlocks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.disk_queue_depth\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.disk_usage.bin_log.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.disk_usage.replication_slot.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.disk_usage.transaction_logs.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.failed_sql_server_agent_jobs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.free_local_storage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.free_storage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.freeable_memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.commit\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.ddl\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.dml\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.insert\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.read\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.select\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.update\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.latency.write\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.login_failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.maximum_used_transaction_ids\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.oldest_replication_slot_lag.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.queries\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.read_io.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.replica_lag.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.swap_usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.commit\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.ddl\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.delete\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.dml\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.insert\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.network\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.network_receive\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.network_transmit\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.read\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.select\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.update\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.throughput.write\",\"type\":\"number\",\"esTypes\":[\"float\",\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.transaction_logs_generation\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.transactions.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.transactions.blocked\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.volume_used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.rds.write_io.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_daily_storage.bucket.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_daily_storage.bucket.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_daily_storage.number_of_objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.bucket.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.downloaded.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.errors.4xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.errors.5xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.latency.first_byte.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.latency.total_request.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.delete\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.get\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.head\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.list\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.post\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.put\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.select\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.select_returned.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.select_scanned.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.requests.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.s3_request.uploaded.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.empty_receives\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.delayed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.not_visible\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.messages.visible\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.oldest_message_age.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"aws.sqs.sent_message_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.state.management.enabled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.state.module.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.state.output.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.state.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.acked\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.batches\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.duplicates\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.toomany\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.events.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.read.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.libbeat.output.write.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.runtime.goroutines\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.stats.uptime.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"beat.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_disk.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_disk.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_disk.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_health.overall_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_health.timechecks.epoch\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_health.timechecks.round.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_health.timechecks.round.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.degraded.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.degraded.ratio\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.degraded.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.misplace.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.misplace.ratio\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.misplace.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.epoch\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.full\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.nearfull\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.num_in_osds\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.num_osds\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.num_remapped_pgs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.osd.num_up_osds\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg.avail_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg.data_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg.total_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg.used_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg_state.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg_state.state_name\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.pg_state.version\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.traffic.read_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.traffic.read_op_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.traffic.write_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.traffic.write_op_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.cluster_status.version\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.available.kb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.available.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.health\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.last_updated\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.store_stats.last_updated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.store_stats.log.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.store_stats.misc.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.store_stats.sst.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.store_stats.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.total.kb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.monitor_health.used.kb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.device_class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.pg_num\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.total.byte\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.used.byte\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_df.used.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.children\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.crush_weight\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.depth\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.device_class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.exists\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.father\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.primary_affinity\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.reweight\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.osd_tree.type_id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.stats.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.stats.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.stats.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ceph.pool_disk.stats.used.kb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"client.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.account.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.availability_zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.image.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.machine.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.project.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.provider\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.region\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.autopilot.healthy\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.alloc.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.garbage_collector.pause.current.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.garbage_collector.pause.total.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.garbage_collector.runs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.goroutines\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.heap_objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.malloc_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"consul.agent.runtime.sys.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.tag\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.runtime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.cache.hits.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.cache.misses.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.do.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.duration.ns.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.duration.ns.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.request.type.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.response.rcode.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.response.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.dns.response.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.panic.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.proto\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.rcode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.server\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"coredns.stats.zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.data.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.disk.fetches\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.disk.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.item_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.memory.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.quota.ram.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.quota.use.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.bucket.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.hdd.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.hdd.quota.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.hdd.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.hdd.used.by_data.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.hdd.used.value.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.max_bucket_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.quota.index_memory.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.quota.memory.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.quota.total.per_node.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.quota.total.value.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.quota.used.per_node.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.quota.used.value.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.used.by_data.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.cluster.ram.used.value.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.cmd_get\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.docs.data_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.docs.disk_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.spatial.data_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.spatial.disk_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.views.data_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.couch.views.disk_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.cpu_utilization_rate.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.current_items.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.current_items.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.ep_bg_fetched\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.get_hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.mcd_memory.allocated.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.mcd_memory.reserved.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.memory.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.memory.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.memory.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.swap.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.swap.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.uptime.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchbase.node.vb_replica_curr_items\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.auth_cache_hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.auth_cache_misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.database_reads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.database_writes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.open_databases\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.open_os_files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.couchdb.request_time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd.bulk_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd.clients_requesting_changes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd.temporary_view_reads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd.view_reads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.COPY\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.DELETE\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.GET\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.HEAD\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.POST\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_request_methods.PUT\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.200\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.201\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.202\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.301\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.304\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.400\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.401\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.403\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.404\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.405\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.409\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.412\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"couchdb.server.httpd_status_codes.500\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"destination.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.data\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.ttl\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.answers.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.header_flags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.op_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.class\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.registered_domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.question.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.resolved_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.response_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"dns.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.command\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.ip_addresses\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.size.root_fs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.size.rw\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.container.tags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.kernel.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.kernel.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.system.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.system.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.total.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.user.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.cpu.user.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.read.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.read.rate\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.reads\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.summary.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.summary.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.summary.rate\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.total\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.write.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.write.rate\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.diskio.writes\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.actor.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.from\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.event.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.event.end_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.event.exit_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.event.output\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.event.start_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.failingstreak\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.healthcheck.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.id.current\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.id.parent\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.size.regular\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.size.virtual\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.image.tags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.containers.paused\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.containers.running\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.containers.stopped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.containers.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.info.images\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.commit.peak\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.commit.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.fail.count\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.private_working_set.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.rss.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.rss.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.usage.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.usage.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.memory.usage.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.in.dropped\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.in.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.in.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.inbound.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.inbound.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.inbound.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.inbound.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.interface\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.out.dropped\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.out.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.out.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.outbound.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.outbound.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.outbound.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"docker.network.outbound.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ecs.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.follower.global_checkpoint\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.follower.index\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.follower.operations_written\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.follower.shard.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.follower.time_since_last_read.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.leader.index\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ccr.leader.max_seq_no\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.pending_task.insert_order\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.pending_task.priority\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.pending_task.source\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.pending_task.time_in_queue.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.state.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.indices.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.indices.fielddata.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.indices.shards.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.indices.shards.primaries\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.nodes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.nodes.data\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.nodes.master\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.cluster.stats.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.primary\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.source.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.source.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.source.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.stage\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.target.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.target.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.target.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.recovery.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.primaries.docs.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.primaries.docs.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.primaries.segments.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.primaries.segments.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.primaries.store.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.total.docs.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.total.docs.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.total.segments.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.total.segments.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.summary.total.store.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.total.docs.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.total.docs.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.total.segments.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.total.segments.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.index.total.store.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ml.job.data_counts.invalid_date_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ml.job.data_counts.processed_record_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ml.job.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.ml.job.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.jvm.memory.heap.init.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.jvm.memory.heap.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.jvm.memory.nonheap.init.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.jvm.memory.nonheap.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.jvm.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.process.mlockall\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.fs.summary.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.fs.summary.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.fs.summary.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.indices.docs.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.indices.docs.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.indices.segments.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.indices.segments.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.indices.store.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.gc.collectors.old.collection.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.gc.collectors.old.collection.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.gc.collectors.young.collection.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.gc.collectors.young.collection.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.old.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.old.peak.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.old.peak_max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.old.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.survivor.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak_max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.survivor.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.young.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.young.peak.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.young.peak_max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.stats.jvm.mem.pools.young.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.node.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.shard.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.shard.primary\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.shard.relocating_node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"elasticsearch.shard.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.cluster_manager.active_clusters\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.cluster_manager.cluster_added\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.cluster_manager.cluster_modified\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.cluster_manager.cluster_removed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.cluster_manager.warming_clusters\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.filesystem.flushed_by_timer\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.filesystem.reopen_failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.filesystem.write_buffered\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.filesystem.write_completed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.filesystem.write_total_buffered\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.header_overflow\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.headers_cb_no_stream\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.rx_messaging_error\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.rx_reset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.too_many_header_frames\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.trailers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.http2.tx_reset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.listener_added\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.listener_create_failure\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.listener_create_success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.listener_modified\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.listener_removed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.total_listeners_active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.total_listeners_draining\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.listener_manager.total_listeners_warming\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.admin_overrides_active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.load_error\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.load_success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.num_keys\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.override_dir_exists\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.runtime.override_dir_not_exists\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.days_until_first_cert_expiring\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.hot_restart_epoch\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.live\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.memory_allocated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.memory_heap_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.parent_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.total_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.version\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.watchdog_mega_miss\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.server.watchdog_miss\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"envoyproxy.server.stats.overflow\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"error.message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"error.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.api_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.disk.backend_commit_duration.ns.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.disk.backend_commit_duration.ns.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.disk.mvcc_db_total_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.disk.wal_fsync_duration.ns.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.disk.wal_fsync_duration.ns.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.counts.followers.counts.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.counts.followers.counts.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.latency.follower.latency.standardDeviation\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.latency.followers.latency.average\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.latency.followers.latency.current\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.latency.followers.latency.maximum\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.followers.latency.followers.latency.minimum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.leader.leader\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.memory.go_memstats_alloc.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.network.client_grpc_received.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.network.client_grpc_sent.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.leaderinfo.leader\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.leaderinfo.starttime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.leaderinfo.uptime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.recv.appendrequest.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.recv.bandwidthrate\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.recv.pkgrate\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.send.appendrequest.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.send.bandwidthrate\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.send.pkgrate\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.starttime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.self.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.grpc_handled.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.grpc_started.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.has_leader\",\"type\":\"number\",\"esTypes\":[\"byte\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.leader_changes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.proposals_committed.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.proposals_failed.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.server.proposals_pending.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.compareanddelete.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.compareanddelete.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.compareandswap.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.compareandswap.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.create.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.create.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.delete.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.delete.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.expire.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.gets.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.gets.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.sets.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.sets.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.update.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.update.success\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"etcd.store.watchers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.category\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.dataset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.end\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.kind\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.module\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.outcome\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.provider\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.risk_score\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.risk_score_norm\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.sequence\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.severity\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.timezone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.accessed\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.ctime\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.device\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.extension\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.gid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.group\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.inode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.mtime\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.owner\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.target_path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"file.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.expvar.cmdline\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.allocated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.frees\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.idle\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.mallocs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.allocations.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.cmdline\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.cpu_fraction\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.next_gc_limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.pause.avg.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.pause.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.pause.max.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.pause.sum.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.total_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.gc.total_pause.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.obtained\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.released\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.stack\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"golang.heap.system.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"graphite.server.example\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.compress.bps.in\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.compress.bps.out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.compress.bps.rate_limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.hard_max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.rate.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.ssl.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.ssl.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.ssl.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.connection.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.idle.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.memory.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.pipes.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.pipes.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.pipes.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.process_num\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.processes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.requests.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.requests.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.run_queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.session.rate.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.session.rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.session.rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.sockets.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.backend.key_rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.backend.key_rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.cache_misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.cached_lookups\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.frontend.key_rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.frontend.key_rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.frontend.session_reuse.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.rate.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ssl.rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.tasks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.ulimit_n\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.uptime.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.zlib_mem_usage.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.info.zlib_mem_usage.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.agent.last\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.down\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.health.fail\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.health.last\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.check.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.client.aborted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.component_type\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.compressor.bypassed.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.compressor.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.compressor.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.compressor.response.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.connection.retried\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.connection.time.avg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.connection.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.downtime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.last_change\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.proxy.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.proxy.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.queue.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.queue.time.avg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.connection.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.denied\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.queued.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.queued.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.redispatched\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.request.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.denied\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.1xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.2xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.3xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.4xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.5xx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.http.other\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.response.time.avg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.selected.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.server.aborted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.server.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.server.backup\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.server.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.service_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.rate.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.rate.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.session.rate.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.throttle.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.tracked.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"haproxy.stat.weight\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.architecture\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.containerized\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.build\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.codename\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.body.content\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.request.referrer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.body.content\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.phrase\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.response.status_code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"http.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.agent.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.secured\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.server.product\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.server.vendor\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.server.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"jolokia.url\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.broker.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.broker.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.broker.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.broker.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.client.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.client.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.client.member_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.error.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.meta\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.partition\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.consumergroup.topic\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.broker.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.broker.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.offset.newest\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.offset.oldest\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.error.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.insync_replica\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.is_leader\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.isr\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.leader\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.partition.replica\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.topic.error.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.topic.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.topic_broker_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.partition.topic_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.topic.error.code\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kafka.topic.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.concurrent_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.host.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.index\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.process.event_loop_delay.ms\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.process.memory.heap.size_limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.process.memory.heap.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.process.memory.heap.uptime.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.process.memory.heap.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.request.disconnects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.request.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.response_time.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.response_time.max.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.snapshot\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.stats.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.status.metrics.concurrent_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.status.metrics.requests.disconnects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.status.metrics.requests.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.status.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kibana.status.status.overall.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.audit.event.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.audit.rejected.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.client.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.etcd.object.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.request.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.request.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.request.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.request.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.response.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.http.response.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.process.cpu.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.process.fds.open.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.process.memory.resident.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.process.memory.virtual.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.process.started.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.client\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.component\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.content_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.current.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.dry_run\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.group\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.handler\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.kind\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.latency.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.latency.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.longrunning.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.resource\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.scope\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.subresource\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.verb\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.apiserver.request.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.limit.cores\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.limit.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.request.cores\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.request.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.usage.core.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.usage.limit.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.usage.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.cpu.usage.node.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.image\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.inodes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.inodes.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.inodes.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.logs.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.majorpagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.pagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.request.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.usage.limit.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.usage.node.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.memory.workingset.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.rootfs.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.rootfs.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.rootfs.inodes.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.rootfs.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.status.phase\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.status.ready\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.status.reason\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.container.status.restarts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.client.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.handler\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.request.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.request.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.request.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.request.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.response.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.http.response.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.leader.is_master\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.node.collector.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.node.collector.eviction.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.node.collector.health.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.node.collector.unhealthy.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.process.cpu.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.process.fds.open.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.process.memory.resident.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.process.memory.virtual.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.process.started.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.workqueue.adds.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.workqueue.depth.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.workqueue.longestrunning.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.workqueue.retries.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.workqueue.unfinished.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.controllermanager.zone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.active.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.concurrency\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.created.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.deadline.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.is_suspended\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.last_schedule.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.next_schedule.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.cronjob.schedule\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.paused\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.replicas.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.replicas.desired\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.replicas.unavailable\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.deployment.replicas.updated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.involved_object.api_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.involved_object.kind\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.involved_object.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.involved_object.resource_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.involved_object.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.message\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.namespace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.resource_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.self_link\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.timestamp.created\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.metadata.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.reason\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.timestamp.first_occurrence\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.timestamp.last_occurrence\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.event.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.namespace\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.cpu.allocatable.cores\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.cpu.capacity.cores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.cpu.usage.core.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.cpu.usage.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.inodes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.inodes.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.inodes.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.fs.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.allocatable.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.majorpagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.pagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.memory.workingset.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.network.rx.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.network.rx.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.network.tx.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.network.tx.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.pod.allocatable.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.pod.capacity.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.runtime.imagefs.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.runtime.imagefs.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.runtime.imagefs.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.status.ready\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.node.status.unschedulable\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.cpu.usage.limit.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.cpu.usage.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.cpu.usage.node.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.host_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.major_page_faults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.page_faults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.usage.limit.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.usage.node.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.memory.working_set.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.network.rx.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.network.rx.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.network.tx.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.network.tx.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.status.phase\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.status.ready\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.status.scheduled\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.pod.uid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.client.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.handler\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.request.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.request.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.request.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.request.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.response.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.http.response.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.process.cpu.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.process.fds.open.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.process.memory.resident.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.process.memory.virtual.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.process.started.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.sync.networkprogramming.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.sync.networkprogramming.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.sync.rules.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.proxy.sync.rules.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.replicas.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.replicas.desired\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.replicas.labeled\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.replicas.observed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.replicaset.replicas.ready\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.client.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.handler\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.request.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.request.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.request.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.request.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.request.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.response.size.bytes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.http.response.size.bytes.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.leader.is_master\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.method\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.operation\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.process.cpu.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.process.fds.open.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.process.memory.resident.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.process.memory.virtual.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.process.started.sec\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.result\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.duration.seconds.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.duration.seconds.sum\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.e2e.duration.us.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.e2e.duration.us.sum\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.pod.attempts.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.scheduler.scheduling.pod.preemption.victims.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.created\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.generation.desired\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.generation.observed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.replicas.desired\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.statefulset.replicas.observed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.container\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.cpu.usage.core.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.cpu.usage.nanocores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.memory.majorpagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.memory.pagefaults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.memory.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.memory.workingset.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.system.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.available.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.capacity.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.inodes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.inodes.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.inodes.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.fs.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kubernetes.volume.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kvm.dommemstat.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kvm.dommemstat.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kvm.dommemstat.stat.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"kvm.dommemstat.stat.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.level\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.logger\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"log.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"logstash.node.jvm.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"logstash.node.stats.events.filtered\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"logstash.node.stats.events.in\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"logstash.node.stats.events.out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.bytes.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.bytes.limit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.cmd.get\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.cmd.set\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.connections.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.connections.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.evictions\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.get.hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.get.misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.items.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.items.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.threads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.uptime.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memcached.stats.written.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"metricset.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"metricset.period\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.collection\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.commands.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.commands.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.db\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.getmore.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.getmore.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.insert.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.insert.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.lock.read.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.lock.read.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.lock.write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.lock.write.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.queries.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.queries.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.remove.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.remove.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.total.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.total.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.update.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.collstats.update.time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.avg_obj_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.collections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.data_file_version.major\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.data_file_version.minor\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.data_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.db\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.extent_free_list.num\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.extent_free_list.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.file_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.index_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.indexes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.ns_size_mb.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.num_extents\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.objects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.dbstats.storage_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.aggregate.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.aggregate.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.build_info.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.build_info.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.coll_stats.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.coll_stats.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.connection_pool_stats.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.connection_pool_stats.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.count.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.count.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.db_stats.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.db_stats.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.distinct.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.distinct.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.find.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.find.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_cmd_line_opts.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_cmd_line_opts.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_last_error.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_last_error.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_log.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_log.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_more.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_more.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_parameter.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.get_parameter.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.host_info.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.host_info.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.insert.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.insert.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.is_master.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.is_master.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.is_self.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.is_self.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.last_collections.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.last_collections.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.last_commands.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.last_commands.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.list_databased.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.list_databased.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.list_indexes.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.list_indexes.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.ping.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.ping.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.profile.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.profile.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_get_rbid.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_get_rbid.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_get_status.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_get_status.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_heartbeat.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_heartbeat.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_update_position.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.replset_update_position.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.server_status.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.server_status.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.update.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.update.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.whatsmyuri.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.commands.whatsmyuri.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.cursor.open.no_timeout\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.cursor.open.pinned\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.cursor.open.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.cursor.timed_out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.document.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.document.inserted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.document.returned\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.document.updated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.get_last_error.write_timeouts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.get_last_error.write_wait.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.get_last_error.write_wait.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.operation.scan_and_order\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.operation.write_conflicts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.query_executor.scanned_documents\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.query_executor.scanned_indexes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.apply.attempts_to_become_secondary\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.apply.batches.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.apply.batches.time.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.apply.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.buffer.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.buffer.max_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.buffer.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.cancels\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.event_created\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.event_wait\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.dbwork\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.exclusive\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.netcmd\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.work\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.scheduled.work_at\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.counters.waits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.event_waiters\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.network_interface\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.in_progress.dbwork\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.in_progress.exclusive\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.in_progress.network\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.ready\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.queues.sleepers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.shutting_down\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.executor.unsignaled_events\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.initial_sync.completed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.initial_sync.failed_attempts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.initial_sync.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.network.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.network.getmores.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.network.getmores.time.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.network.ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.network.reders_created\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.preload.docs.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.preload.docs.time.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.preload.indexes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.replication.preload.indexes.time.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.storage.free_list.search.bucket_exhausted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.storage.free_list.search.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.storage.free_list.search.scanned\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.ttl.deleted_documents\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.metrics.ttl.passes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.headroom.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.headroom.min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.lag.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.lag.min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.arbiter.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.arbiter.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.down.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.down.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.primary.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.primary.optime\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.recovering.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.recovering.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.rollback.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.rollback.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.secondary.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.secondary.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.secondary.optimes\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.startup2.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.startup2.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.unhealthy.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.unhealthy.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.unknown.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.members.unknown.hosts\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.oplog.first.timestamp\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.oplog.last.timestamp\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.oplog.size.allocated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.oplog.size.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.oplog.window\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.optimes.applied\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.optimes.durable\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.optimes.last_committed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.server_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.replstatus.set_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.asserts.msg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.asserts.regular\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.asserts.rollovers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.asserts.user\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.asserts.warning\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.background_flushing.average.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.background_flushing.flushes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.background_flushing.last.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.background_flushing.last_finished\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.background_flushing.total.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.connections.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.connections.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.connections.total_created\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.extra_info.heap_usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.extra_info.page_faults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.active_clients.readers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.active_clients.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.active_clients.writers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.current_queue.readers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.current_queue.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.current_queue.writers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.global_lock.total_time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.commits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.commits_in_write_lock\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.compression\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.early_commits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.journaled.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.commits.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.commits_in_write_lock.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.dt.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.prep_log_buffer.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.remap_private_view.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.write_to_data_files.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.times.write_to_journal.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.journaling.write_to_data_files.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.local_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.acquire.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.acquire.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.acquire.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.acquire.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.deadlock.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.deadlock.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.deadlock.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.deadlock.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.us.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.us.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.us.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.collection.wait.us.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.acquire.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.acquire.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.acquire.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.acquire.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.deadlock.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.deadlock.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.deadlock.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.deadlock.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.us.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.us.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.us.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.database.wait.us.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.acquire.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.acquire.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.acquire.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.acquire.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.deadlock.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.deadlock.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.deadlock.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.deadlock.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.us.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.us.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.us.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.global.wait.us.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.acquire.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.acquire.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.acquire.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.acquire.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.deadlock.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.deadlock.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.deadlock.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.deadlock.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.us.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.us.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.us.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.meta_data.wait.us.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.acquire.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.acquire.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.acquire.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.acquire.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.deadlock.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.deadlock.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.deadlock.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.deadlock.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.count.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.count.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.count.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.count.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.us.R\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.us.W\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.us.r\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.locks.oplog.wait.us.w\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.memory.bits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.memory.mapped.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.memory.mapped_with_journal.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.memory.resident.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.memory.virtual.mb\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.network.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.network.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.network.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.command\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.delete\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.getmore\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.insert\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.query\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.counters.update\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.commands.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.commands.latency\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.reads.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.reads.latency\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.writes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.latencies.writes.latency\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.command\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.delete\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.getmore\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.insert\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.query\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.ops.replicated.update\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.process\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.storage_engine.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.uptime.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.dirty.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.maximum.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.pages.evicted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.pages.read\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.pages.write\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.cache.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.read.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.read.out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.read.total_tickets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.write.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.write.out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.concurrent_transactions.write.total_tickets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.flushes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.max_file_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.scans\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.syncs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.wired_tiger.log.writes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mongodb.status.write_backs_queued\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.database.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.database.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.active_temp_tables\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.batch_requests_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.buffer.cache_hit.pct\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.buffer.checkpoint_pages_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.buffer.database_pages\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.buffer.page_life_expectancy.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.buffer.target_pages\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.compilations_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.connections_reset_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.lock_waits_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.logins_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.logouts_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.page_splits_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.recompilations_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.transactions\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.performance.user_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.space_usage.since_last_backup.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.space_usage.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.space_usage.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.space_usage.used.pct\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.stats.active_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.stats.backup_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.stats.recovery_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.stats.since_last_checkpoint.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mssql.transaction_log.stats.total_size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"munin.plugin.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.apply.oooe\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.apply.oool\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.apply.window\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cert.deps_distance\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cert.index_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cert.interval\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cluster.conf_id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cluster.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.cluster.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.commit.oooe\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.commit.window\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.connected\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.evs.evict\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.evs.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.flow_ctl.paused\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.flow_ctl.paused_ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.flow_ctl.recv\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.flow_ctl.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.last_committed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.bf_aborts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.cert_failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.commits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.recv.queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.recv.queue_avg\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.recv.queue_max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.recv.queue_min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.replays\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.send.queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.send.queue_avg\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.send.queue_max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.send.queue_min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.local.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.ready\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.received.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.received.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.data_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.keys\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.keys_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.galera_status.repl.other_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.aborted.clients\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.aborted.connects\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.binlog.cache.disk_use\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.binlog.cache.use\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.bytes.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.bytes.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.command.delete\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.command.insert\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.command.select\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.command.update\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.created.tmp.disk_tables\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.created.tmp.files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.created.tmp.tables\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.delayed.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.delayed.insert_threads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.delayed.writes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.flush_commands\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.commit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.delete\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.external_lock\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.mrr_init\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.prepare\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.first\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.key\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.last\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.next\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.prev\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.rnd\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.read.rnd_next\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.rollback\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.savepoint\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.savepoint_rollback\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.update\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.handler.write\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.bytes.data\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.bytes.dirty\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.dump_status\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.load_status\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.data\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.dirty\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.flushed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.latched\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.misc\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pages.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pool.reads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pool.resize_status\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.pool.wait_free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.read.ahead\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.read.ahead_evicted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.read.ahead_rnd\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.read.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.innodb.buffer_pool.write_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.max_used_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.open.files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.open.streams\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.open.tables\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.opened_tables\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.queries\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.questions\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.threads.cached\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.threads.connected\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.threads.created\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"mysql.status.threads.running\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.connections.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.routes.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.server.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.server.time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.cores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.cpu\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.http.req_stats.uri.connz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.http.req_stats.uri.root\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.http.req_stats.uri.routez\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.http.req_stats.uri.subsz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.http.req_stats.uri.varz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.in.messages\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.mem.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.out.messages\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.remotes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.slow_consumers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.total_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.stats.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.cache.fanout.avg\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.cache.fanout.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.cache.hit_rate\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.cache.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.inserts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.matches\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.removes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nats.subscriptions.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.application\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.community_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.direction\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.forwarded_ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.iana_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.protocol\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.transport\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"network.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.accepts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.handled\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.reading\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.waiting\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nginx.stubstatus.writing\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.vendor\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"observer.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.buffer_pool\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cache.buffer.hit.pct\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cache.get.consistent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cache.get.db_blocks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cache.physical_reads\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.avg\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.cache_hit.pct\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.max\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.opened.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.opened.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.parse.real\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.parse.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.session.cache_hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.cursors.total\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.io_reloads\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.lock_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.machine\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.pin_requests\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.performance.username\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.online_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.size.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.size.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.size.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.data_file.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.space.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.space.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"oracle.tablespace.space.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.connections.accepted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.connections.listen_queue_len\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.connections.max_listen_queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.connections.queued\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.process_manager\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.processes.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.processes.idle\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.processes.max_active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.processes.max_children_reached\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.processes.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.slow_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.start_since\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.pool.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.last_request_cpu\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.last_request_memory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.request_duration\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.script\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.start_since\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"php_fpm.process.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.application_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.backend_start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.client.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.client.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.client.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.database.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.database.oid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.query\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.query_start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.state_change\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.transaction_start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.user.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.activity.waiting\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.allocated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.backend\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.backend_fsync\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.checkpoints\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.clean\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.buffers.clean_full\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.checkpoints.requested\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.checkpoints.scheduled\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.checkpoints.times.sync.ms\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.checkpoints.times.write.ms\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.bgwriter.stats_reset\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.blocks.hit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.blocks.read\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.blocks.time.read.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.blocks.time.write.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.conflicts\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.deadlocks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.number_of_backends\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.oid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.rows.deleted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.rows.fetched\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.rows.inserted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.rows.returned\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.rows.updated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.stats_reset\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.temporary.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.temporary.files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.transactions.commit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.database.transactions.rollback\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.database.oid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.calls\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.local.dirtied\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.local.hit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.local.read\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.local.written\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.shared.dirtied\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.shared.hit\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.shared.read\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.shared.written\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.temp.read\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.memory.temp.written\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.rows\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.text\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.time.max.ms\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.time.mean.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.time.min.ms\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.time.stddev.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.query.time.total.ms\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"postgresql.statement.user.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.args\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.executable\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.md5\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha256\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.hash.sha512\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pgid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.ppid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.start\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.thread.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.thread.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.title\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"process.working_directory\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.channel_max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.channels\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.client_provided.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.frame_max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.octet_count.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.octet_count.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.packet_count.pending\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.packet_count.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.packet_count.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.peer.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.peer.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.connection.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.auto_delete\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.durable\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.internal\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.messages.publish_in.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.messages.publish_in.details.rate\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.messages.publish_out.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.messages.publish_out.details.rate\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.exchange.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.disk.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.disk.free.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.fd.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.fd.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.gc.num.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.gc.reclaimed.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.file_handle.open_attempt.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.file_handle.open_attempt.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.read.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.read.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.reopen.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.seek.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.seek.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.sync.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.sync.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.write.avg.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.io.write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.mem.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.mem.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.mnesia.disk.tx.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.mnesia.ram.tx.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.msg.store_read.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.msg.store_write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.proc.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.proc.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.processors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.queue.index.journal_write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.queue.index.read.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.queue.index.write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.run.queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.socket.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.socket.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.node.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.arguments.max_priority\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.auto_delete\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.consumers.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.consumers.utilisation.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.disk.reads.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.disk.writes.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.durable\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.exclusive\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.memory.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.persistent.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.ready.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.ready.details.rate\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.total.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.total.details.rate\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.unacknowledged.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.messages.unacknowledged.details.rate\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.queue.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"rabbitmq.vhost\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.biggest_input_buf\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.blocked\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.connected\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.longest_output_list\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.max_input_buffer\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.clients.max_output_buffer\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.cluster.enabled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.cpu.used.sys\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.cpu.used.sys_children\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.cpu.used.user\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.cpu.used.user_children\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.active_defrag.is_running\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.allocated\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.fragmentation.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.fragmentation.ratio\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.resident\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.allocator_stats.rss.ratio\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.fragmentation.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.fragmentation.ratio\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.max.policy\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.max.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.used.dataset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.used.lua\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.used.peak\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.used.rss\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.memory.used.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.bgrewrite.last_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.buffer.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.copy_on_write.last_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.enabled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.fsync.delayed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.fsync.pending\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.rewrite.buffer.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.rewrite.current_time.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.rewrite.in_progress\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.rewrite.last_time.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.rewrite.scheduled\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.size.base\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.size.current\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.aof.write.last_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.loading\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.bgsave.current_time.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.bgsave.in_progress\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.bgsave.last_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.bgsave.last_time.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.copy_on_write.last_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.last_save.changes_since\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.persistence.rdb.last_save.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.backlog.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.backlog.first_byte_offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.backlog.histlen\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.backlog.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.connected_slaves\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.last_io_seconds_ago\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.link_status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.second_offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.sync.in_progress\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.sync.last_io_seconds_ago\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master.sync.left_bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.master_offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.role\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.slave.is_readonly\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.slave.offset\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.replication.slave.priority\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.arch_bits\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.build_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.config_file\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.gcc_version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.git_dirty\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.git_sha1\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.hz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.lru_clock\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.mode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.multiplexing_api\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.run_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.tcp_port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.server.uptime\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.slowlog.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.active_defrag.hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.active_defrag.key_hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.active_defrag.key_misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.active_defrag.misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.commands_processed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.connections.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.connections.rejected\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.instantaneous.input_kbps\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.instantaneous.ops_per_sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.instantaneous.output_kbps\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.keys.evicted\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.keys.expired\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.keyspace.hits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.keyspace.misses\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.latest_fork_usec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.migrate_cached_sockets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.net.input.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.net.output.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.pubsub.channels\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.pubsub.patterns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.slave_expires_tracked_keys\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.sync.full\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.sync.partial.err\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.info.stats.sync.partial.ok\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.key.expire.ttl\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.key.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.key.length\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.key.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.key.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.keyspace.avg_ttl\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.keyspace.expires\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.keyspace.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"redis.keyspace.keys\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"related.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"server.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.ephemeral_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"service.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.address\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.number\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.as.organization.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.country_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.region_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.geo.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.mac\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.nat.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.nat.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source.user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.idle.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.idle.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.iowait.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.iowait.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.irq.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.irq.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.nice.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.nice.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.softirq.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.softirq.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.steal.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.steal.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.system.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.system.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.user.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.core.user.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.cores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.idle.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.idle.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.idle.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.iowait.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.iowait.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.iowait.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.irq.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.irq.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.irq.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.nice.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.nice.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.nice.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.softirq.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.softirq.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.softirq.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.steal.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.steal.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.steal.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.system.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.system.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.system.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.total.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.total.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":3,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.user.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.user.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.cpu.user.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.io.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.await\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.busy\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.queue.avg_size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.read.await\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.read.per_sec.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.read.request.merges_per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.read.request.per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.request.avg_size\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.service_time\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.write.await\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.write.per_sec.bytes\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.write.request.merges_per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.iostat.write.request.per_sec\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.read.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.read.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.read.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.serial_number\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.write.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.write.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.diskio.write.time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.entropy.available_bits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.entropy.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.available\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.device_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.free_files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.mount_point\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.filesystem.used.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.fsstat.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.fsstat.total_files\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.fsstat.total_size.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.fsstat.total_size.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.fsstat.total_size.used\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.1\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.15\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.5\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.cores\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.norm.1\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.norm.15\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.load.norm.5\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.actual.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.actual.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.actual.used.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.default_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.reserved\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.surplus\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.hugepages.used.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.swap.free\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.swap.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.swap.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.swap.used.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.memory.used.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.in.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.in.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.in.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.in.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.out.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.out.dropped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.out.errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.network.out.packets\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.blkio.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.blkio.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.blkio.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.blkio.total.ios\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.cfs.period.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.cfs.quota.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.cfs.shares\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.rt.period.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.rt.runtime.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.stats.periods\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.stats.throttled.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpu.stats.throttled.periods\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpuacct.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpuacct.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpuacct.stats.system.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpuacct.stats.user.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.cpuacct.total.ns\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem.usage.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem_tcp.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem_tcp.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem_tcp.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.kmem_tcp.usage.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.mem.usage.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.memsw.failures\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.memsw.limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.memsw.usage.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.memsw.usage.max.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.active_anon.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.active_file.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.cache.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.hierarchical_memory_limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.hierarchical_memsw_limit.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.inactive_anon.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.inactive_file.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.major_page_faults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.mapped_file.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.page_faults\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.pages_in\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.pages_out\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.rss_huge.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.swap.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.memory.stats.unevictable.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cgroup.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cmdline\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.start_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.system.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.norm.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.total.value\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.cpu.user.ticks\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.fd.limit.hard\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.fd.limit.soft\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.fd.open\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.rss.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.rss.pct\",\"type\":\"number\",\"esTypes\":[\"scaled_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.share\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.memory.size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.dead\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.idle\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.running\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.sleeping\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.stopped\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.unknown\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.process.summary.zombie\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.blocks.synced\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.blocks.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.disks.active\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.disks.failed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.disks.spare\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.disks.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.level\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.raid.sync_action\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.local.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.local.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.process.cmdline\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.remote.etld_plus_one\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.remote.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.remote.host_error\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.remote.ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.remote.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.all.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.all.listening\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.close_wait\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.established\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.listening\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.orphan\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.all.time_wait\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.tcp.memory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.udp.all.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.socket.summary.udp.memory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"system.uptime.duration.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timeseries.instance\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tracing.trace.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"tracing.transaction.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"traefik.health.response.avg_time.us\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"traefik.health.response.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"traefik.health.uptime.sec\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.fragment\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.password\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.path\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.port\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.query\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.scheme\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url.username\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.domain\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.full_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.group.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.group.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.hash\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.device.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.original\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.family\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.full\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.kernel\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.platform\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.os.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user_agent.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.read_errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.requests.offloaded\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.requests.routed\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.requests.static\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.requests.total\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.worker_pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.core.write_errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.total.exceptions\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.total.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.total.read_errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.total.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.total.write_errors\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.accepting\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.avg_rt\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.delta_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.exceptions\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.harakiri_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.respawn_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.rss\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.running_time\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.signal_queue\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.signals\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.status\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.tx\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"uwsgi.status.worker.vsz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.capacity.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.capacity.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.capacity.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.capacity.used.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.fstype\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.datastore.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.cpu.free.mhz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.cpu.total.mhz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.cpu.used.mhz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.memory.free.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.memory.total.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.memory.used.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.host.network_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.cpu.used.mhz\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.host\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.memory.free.guest.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.memory.total.guest.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.memory.used.guest.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.memory.used.host.bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.network_names\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"vsphere.virtualmachine.os\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.display_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.exit_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.path_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.pid\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.start_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.start_type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"windows.service.uptime.ms\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.connection.interest_ops\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.connection.queued\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.connection.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.connection.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.approximate_data_size\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.ephemerals_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.followers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.hostname\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.latency.avg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.latency.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.latency.min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.max_file_descriptor_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.num_alive_connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.open_file_descriptor_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.outstanding_requests\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.packets.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.packets.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.pending_syncs\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.server_state\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.synced_followers\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.version\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.watch_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.mntr.znode_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.connections\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.epoch\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.latency.avg\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.latency.max\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.latency.min\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.mode\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.node_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.outstanding\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.received\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.sent\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.version_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"zookeeper.server.zxid\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", - "timeFieldName" : "@timestamp", - "title" : "metricbeat-*" - }, - "type" : "index-pattern", - "migrationVersion" : { - "index-pattern" : "7.6.0" - }, - "updated_at" : "2020-01-22T15:34:59.061Z" + "id": "custom-space:index-pattern:metricbeat-*", + "index": ".kibana_1", + "source": { + "index-pattern": { + "fieldFormatMap": "{\"aerospike.namespace.device.available.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.device.free.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.device.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.device.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.free.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.memory.used.data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.index.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.sindex.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"aws.rds.disk_usage.bin_log.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.free_local_storage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.free_storage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.freeable_memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.latency.commit\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.ddl\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.dml\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.insert\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.read\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.select\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.update\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.write\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.replica_lag.sec\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.swap_usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.volume_used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_daily_storage.bucket.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.downloaded.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.latency.first_byte.ms\":{\"id\":\"duration\",\"params\":{}},\"aws.s3_request.latency.total_request.ms\":{\"id\":\"duration\",\"params\":{}},\"aws.s3_request.requests.select_returned.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.requests.select_scanned.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.uploaded.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.sqs.oldest_message_age.sec\":{\"id\":\"duration\",\"params\":{}},\"aws.sqs.sent_message_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.degraded.ratio\":{\"id\":\"percent\",\"params\":{}},\"ceph.cluster_status.misplace.ratio\":{\"id\":\"percent\",\"params\":{}},\"ceph.cluster_status.pg.avail_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.data_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.total_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.used_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.traffic.read_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.traffic.write_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.log.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.misc.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.sst.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.total.byte\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.used.byte\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.used.pct\":{\"id\":\"percent\",\"params\":{}},\"ceph.pool_disk.stats.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.pool_disk.stats.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"client.bytes\":{\"id\":\"bytes\",\"params\":{}},\"client.nat.port\":{\"id\":\"string\",\"params\":{}},\"client.port\":{\"id\":\"string\",\"params\":{}},\"coredns.stats.dns.request.duration.ns.sum\":{\"id\":\"duration\",\"params\":{}},\"couchbase.bucket.data.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.disk.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.quota.ram.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.quota.use.pct\":{\"id\":\"percent\",\"params\":{}},\"couchbase.cluster.hdd.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.quota.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.used.by_data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.total.per_node.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.total.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.used.per_node.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.used.by_data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.couch.docs.data_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.couch.docs.disk_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.mcd_memory.allocated.bytes\":{\"id\":\"bytes\",\"params\":{}},\"destination.bytes\":{\"id\":\"bytes\",\"params\":{}},\"destination.nat.port\":{\"id\":\"string\",\"params\":{}},\"destination.port\":{\"id\":\"string\",\"params\":{}},\"docker.cpu.core.*.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.kernel.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.system.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.user.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.diskio.summary.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.commit.peak\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.commit.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.limit\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.private_working_set.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.rss.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.memory.rss.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.usage.max\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.usage.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.memory.usage.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.inbound.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.outbound.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.primaries.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.primaries.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.total.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.total.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.total.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.total.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.heap.init.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.heap.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.nonheap.init.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.nonheap.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.indices.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.disk.mvcc_db_total_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.memory.go_memstats_alloc.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.network.client_grpc_received.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.network.client_grpc_sent.bytes\":{\"id\":\"bytes\",\"params\":{}},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\",\"params\":{}},\"event.severity\":{\"id\":\"string\",\"params\":{}},\"golang.heap.allocations.active\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.allocated\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.idle\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.total\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.gc.next_gc_limit\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.obtained\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.released\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.stack\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.total\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.info.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"haproxy.info.memory.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.info.ssl.frontend.session_reuse.pct\":{\"id\":\"percent\",\"params\":{}},\"haproxy.stat.compressor.bypassed.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.response.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.throttle.pct\":{\"id\":\"percent\",\"params\":{}},\"http.request.body.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.request.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.body.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.status_code\":{\"id\":\"string\",\"params\":{}},\"kibana.stats.process.memory.heap.size_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kibana.stats.process.memory.heap.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kibana.stats.process.memory.heap.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.cpu.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.cpu.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.logs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.logs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.logs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.request.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.memory.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.allocatable.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.network.rx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.network.tx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.cpu.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.cpu.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.working_set.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.network.rx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.network.tx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.avg_obj_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.data_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.extent_free_list.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.file_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.index_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.storage_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.replstatus.headroom.max\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.headroom.min\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.lag.max\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.lag.min\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.oplog.size.allocated\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.replstatus.oplog.size.used\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.extra_info.heap_usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.dirty.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.maximum.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.max_file_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mysql.status.bytes.received\":{\"id\":\"bytes\",\"params\":{}},\"mysql.status.bytes.sent\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.cpu\":{\"id\":\"percent\",\"params\":{}},\"nats.stats.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.mem.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.uptime\":{\"id\":\"duration\",\"params\":{}},\"nats.subscriptions.cache.hit_rate\":{\"id\":\"percent\",\"params\":{}},\"network.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"process.pgid\":{\"id\":\"string\",\"params\":{}},\"process.pid\":{\"id\":\"string\",\"params\":{}},\"process.ppid\":{\"id\":\"string\",\"params\":{}},\"process.thread.id\":{\"id\":\"string\",\"params\":{}},\"rabbitmq.connection.frame_max\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.disk.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.disk.free.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.gc.reclaimed.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.io.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.io.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.mem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.queue.consumers.utilisation.pct\":{\"id\":\"percent\",\"params\":{}},\"rabbitmq.queue.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.active\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.allocated\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.fragmentation.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.resident\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.fragmentation.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.max.value\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.dataset\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.lua\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.peak\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.rss\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.value\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.buffer.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.copy_on_write.last_size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.rewrite.buffer.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.rewrite.current_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.aof.rewrite.last_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.aof.size.base\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.size.current\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.rdb.bgsave.current_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.rdb.bgsave.last_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.rdb.copy_on_write.last_size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.replication.backlog.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.replication.master.last_io_seconds_ago\":{\"id\":\"duration\",\"params\":{}},\"redis.info.replication.master.sync.last_io_seconds_ago\":{\"id\":\"duration\",\"params\":{}},\"redis.info.replication.master.sync.left_bytes\":{\"id\":\"bytes\",\"params\":{}},\"server.bytes\":{\"id\":\"bytes\",\"params\":{}},\"server.nat.port\":{\"id\":\"string\",\"params\":{}},\"server.port\":{\"id\":\"string\",\"params\":{}},\"source.bytes\":{\"id\":\"bytes\",\"params\":{}},\"source.nat.port\":{\"id\":\"string\",\"params\":{}},\"source.port\":{\"id\":\"string\",\"params\":{}},\"system.core.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.iowait.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.irq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.nice.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.softirq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.steal.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.system.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.user.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.idle.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.iowait.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.iowait.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.irq.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.irq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.nice.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.nice.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.softirq.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.softirq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.steal.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.steal.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.system.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.system.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.total.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.user.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.user.pct\":{\"id\":\"percent\",\"params\":{}},\"system.diskio.iostat.read.per_sec.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.iostat.write.per_sec.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.entropy.pct\":{\"id\":\"percent\",\"params\":{}},\"system.filesystem.available\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.free\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.total\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.fsstat.total_size.free\":{\"id\":\"bytes\",\"params\":{}},\"system.fsstat.total_size.total\":{\"id\":\"bytes\",\"params\":{}},\"system.fsstat.total_size.used\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.default_size\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.free\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.reserved\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.surplus\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.total\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.swap.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.total\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.total\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.blkio.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.active_anon.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.active_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.cache.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.hierarchical_memory_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.hierarchical_memsw_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.inactive_anon.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.inactive_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.mapped_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.rss_huge.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.swap.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.unevictable.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.memory.rss.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.memory.share\":{\"id\":\"bytes\",\"params\":{}},\"system.process.memory.size\":{\"id\":\"bytes\",\"params\":{}},\"system.socket.summary.tcp.memory\":{\"id\":\"bytes\",\"params\":{}},\"system.socket.summary.udp.memory\":{\"id\":\"bytes\",\"params\":{}},\"system.uptime.duration.ms\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\"}},\"url.port\":{\"id\":\"string\",\"params\":{}},\"vsphere.datastore.capacity.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.used.pct\":{\"id\":\"percent\",\"params\":{}},\"vsphere.host.memory.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.host.memory.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.host.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.free.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.total.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.used.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.used.host.bytes\":{\"id\":\"bytes\",\"params\":{}},\"windows.service.uptime.ms\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\"}}}", + "timeFieldName": "@timestamp", + "title": "metricbeat-*" + }, + "migrationVersion": { + "index-pattern": "7.6.0" + }, + "type": "index-pattern", + "updated_at": "2020-01-22T15:34:59.061Z" } } } @@ -145,19 +145,19 @@ { "type": "doc", "value": { - "index": ".kibana", - "type": "doc", "id": "index-pattern:logstash-*", + "index": ".kibana_1", "source": { "index-pattern": { - "title": "logstash-*", "timeFieldName": "@timestamp", - "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"runtime_number\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]" + "title": "logstash-*" }, - "type": "index-pattern", "migrationVersion": { - "index-pattern": "6.5.0" + "index-pattern": "7.11.0" }, + "references": [ + ], + "type": "index-pattern", "updated_at": "2018-12-21T00:43:07.096Z" } } @@ -166,20 +166,20 @@ { "type": "doc", "value": { - "index": ".kibana", - "type": "doc", "id": "custom_space:index-pattern:logstash-*", + "index": ".kibana_1", "source": { - "namespace": "custom_space", "index-pattern": { - "title": "logstash-*", "timeFieldName": "@timestamp", - "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]" + "title": "logstash-*" }, - "type": "index-pattern", "migrationVersion": { - "index-pattern": "6.5.0" + "index-pattern": "7.11.0" }, + "namespace": "custom_space", + "references": [ + ], + "type": "index-pattern", "updated_at": "2018-12-21T00:43:07.096Z" } } @@ -188,22 +188,31 @@ { "type": "doc", "value": { - "index": ".kibana", - "type": "doc", "id": "visualization:i-exist", + "index": ".kibana_1", "source": { + "migrationVersion": { + "visualization": "7.12.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "updated_at": "2019-01-22T19:32:31.206Z", "visualization": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, "title": "A Pie", - "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}", "uiStateJSON": "{}", - "description": "", "version": 1, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}" - } - }, - "type": "visualization", - "updated_at": "2019-01-22T19:32:31.206Z" + "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}" + } } } } @@ -211,23 +220,32 @@ { "type": "doc", "value": { - "index": ".kibana", - "type": "doc", "id": "custom_space:visualization:i-exist", + "index": ".kibana_1", "source": { + "migrationVersion": { + "visualization": "7.12.0" + }, "namespace": "custom_space", + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "updated_at": "2019-01-22T19:32:31.206Z", "visualization": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, "title": "A Pie", - "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}", "uiStateJSON": "{}", - "description": "", "version": 1, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}" - } - }, - "type": "visualization", - "updated_at": "2019-01-22T19:32:31.206Z" + "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}" + } } } } @@ -235,21 +253,48 @@ { "type": "doc", "value": { - "index": ".kibana", - "type": "doc", "id": "query:OKJpgs", + "index": ".kibana_1", "source": { "query": { - "title": "OKJpgs", "description": "Ok responses for jpg files", + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "index": "b15b1d40-a8bb-11e9-98cf-2bb06ef63e0b", + "key": "extension.raw", + "negate": false, + "params": { + "query": "jpg" + }, + "type": "phrase", + "value": "jpg" + }, + "query": { + "match": { + "extension.raw": { + "query": "jpg", + "type": "phrase" + } + } + } + } + ], "query": { - "query": "response:200", - "language": "kuery" + "language": "kuery", + "query": "response:200" }, - "filters": [{"meta":{"index":"b15b1d40-a8bb-11e9-98cf-2bb06ef63e0b","alias":null,"negate":false,"type":"phrase","key":"extension.raw","value":"jpg","params":{"query":"jpg"},"disabled":false},"query":{"match":{"extension.raw":{"query":"jpg","type":"phrase"}}},"$state":{"store":"appState"}}] + "title": "OKJpgs" }, + "references": [ + ], "type": "query", "updated_at": "2019-07-17T17:54:26.378Z" } } -} +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/visualize/default/mappings.json b/x-pack/test/functional/es_archives/visualize/default/mappings.json index cc9a80589e5ae..a6feafd3922a2 100644 --- a/x-pack/test/functional/es_archives/visualize/default/mappings.json +++ b/x-pack/test/functional/es_archives/visualize/default/mappings.json @@ -1,488 +1,2670 @@ { "type": "index", "value": { - "index": ".kibana", - "settings": { - "index": { - "number_of_shards": "1", - "auto_expand_replicas": "0-1", - "number_of_replicas": "0" + "aliases": { + ".kibana": { } }, + "index": ".kibana_1", "mappings": { - "dynamic": "strict", - "properties": { - "apm-telemetry": { - "properties": { - "has_any_services": { - "type": "boolean" + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "49eb3350984bd2a162914d3776e70cfb", + "api_key_pending_invalidation": "16f515278a295f6245149ad7c5ddedb7", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "0b7746a97518ec67b787d141886ad3c1", + "cases-comments": "8a50736330e953bca91747723a319593", + "cases-configure": "387c5f3a3bda7e0ae0dd4e106f914a69", + "cases-connector-mappings": "6bc7e49411d38be4969dc6aa8bd43776", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "core-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", + "dashboard": "40554caf09725935e2c02e02563a2d07", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "enterprise_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "epm-packages": "0cbbb16506734d341a96aaed65ec6413", + "epm-packages-assets": "44621b2f6052ef966da47b7c3a00f33b", + "exception-list": "67f055ab8c10abd7b2ebfd969b836788", + "exception-list-agnostic": "67f055ab8c10abd7b2ebfd969b836788", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "9511b565b1cc6441a42033db3d5de8e9", + "fleet-agent-events": "e20a508b6e805189356be381dbfac8db", + "fleet-agents": "cb661e8ede2b640c42c8e5ef99db0683", + "fleet-enrollment-api-keys": "a69ef7ae661dab31561d6c6f052ef2a7", + "graph-workspace": "27a94b2edcb0610c6aea54a7c56d7752", + "index-pattern": "45915a1ad866812242df474eb0479052", + "infrastructure-ui-source": "3d1b76c39bfb2cc8296b024d73854724", + "ingest-agent-policies": "8b0733cce189659593659dad8db426f0", + "ingest-outputs": "8854f34453a47e26f86a29f8f3b80b4e", + "ingest-package-policies": "c91ca97b1ff700f0fc64dc6b13d65a85", + "ingest_manager_settings": "02a03095f0e05b7a538fa801b88a217f", + "inventory-view": "3d1b76c39bfb2cc8296b024d73854724", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "52346cfec69ff7b47d5f0c12361a2797", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "9134b47593116d7953f6adba096fc463", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "3d1b76c39bfb2cc8296b024d73854724", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-job": "3bb64c31915acf93fc724af137a0891b", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "monitoring-telemetry": "2669d5ec15e82391cf58df4294ee9c68", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "originId": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "959dde12a55b3118eab009d8b2b72ad6", + "search-session": "dfd06597e582fdbbbc09f1a3615e6ce0", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "security-solution-signals-migration": "72761fd374ca11122ac8025a92b84fca", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "d624a677e25046b56e4f111a7b2cc402", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "spaces-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", + "tag": "83d55da58f6530f7055415717ec06474", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "3d1b76c39bfb2cc8296b024d73854724", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "f819cf6636b75c9e76ba733a0c6ef355", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } }, - "services_per_agent": { - "properties": { - "go": { - "type": "long", - "null_value": 0 - }, - "java": { - "type": "long", - "null_value": 0 - }, - "js-base": { - "type": "long", - "null_value": 0 - }, - "nodejs": { - "type": "long", - "null_value": 0 - }, - "python": { - "type": "long", - "null_value": 0 - }, - "ruby": { - "type": "long", - "null_value": 0 + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "executionStatus": { + "properties": { + "error": { + "properties": { + "message": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + } } + }, + "lastExecutionDate": { + "type": "date" + }, + "status": { + "type": "keyword" + } + } + }, + "meta": { + "properties": { + "versionApiKeyLastmodified": { + "type": "keyword" } } + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "notifyWhen": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedAt": { + "type": "date" + }, + "updatedBy": { + "type": "keyword" } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" + } + }, + "api_key_pending_invalidation": { + "properties": { + "apiKeyId": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } }, - "@timestamp": { - "type": "date" + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } }, - "id": { - "type": "text", - "index": false + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } }, - "name": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword" - } + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" } - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "accessibility:disableAnimations": { - "type": "boolean" }, - "buildNum": { - "type": "keyword" + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } }, - "dateFormat:tz": { - "type": "text", + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector": { + "properties": { "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } } + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" } - }, - "defaultIndex": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } } } - }, - "telemetry:optIn": { - "type": "boolean" + } + }, + "settings": { + "properties": { + "syncAlerts": { + "type": "boolean" + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } } } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" + } + }, + "cases-comments": { + "properties": { + "alertId": { + "type": "keyword" + }, + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "index": { + "type": "keyword" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector": { + "properties": { + "fields": { + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } } + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-connector-mappings": { + "properties": { + "mappings": { + "properties": { + "action_type": { + "type": "keyword" + }, + "source": { + "type": "keyword" + }, + "target": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "core-usage-stats": { + "dynamic": "false", + "type": "object" + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "optionsJSON": { + "index": false, + "type": "text" + }, + "panelsJSON": { + "index": false, + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "pause": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "section": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "value": { + "doc_values": false, + "index": false, + "type": "integer" + } + } + }, + "timeFrom": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "timeRestore": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "timeTo": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "enterprise_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "install_source": { + "type": "keyword" + }, + "install_started_at": { + "type": "date" + }, + "install_status": { + "type": "keyword" + }, + "install_version": { + "type": "keyword" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" } }, - "optionsJSON": { - "type": "text" + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } }, - "panelsJSON": { - "type": "text" + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "package_assets": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" + "type": "nested" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "epm-packages-assets": { + "properties": { + "asset_path": { + "type": "keyword" + }, + "data_base64": { + "type": "binary" + }, + "data_utf8": { + "index": false, + "type": "text" + }, + "install_source": { + "type": "keyword" + }, + "media_type": { + "type": "keyword" + }, + "package_name": { + "type": "keyword" + }, + "package_version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } }, - "value": { - "type": "integer" + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" } - }, - "gis-map" : { - "properties" : { - "bounds" : { - "type" : "geo_shape", - "tree" : "quadtree" - }, - "description" : { - "type" : "text" - }, - "layerListJSON" : { - "type" : "text" - }, - "mapStateJSON" : { - "type" : "text" - }, - "title" : { - "type" : "text" - }, - "uiStateJSON" : { - "type" : "text" - }, - "version" : { - "type" : "integer" + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "ack_data": { + "type": "text" + }, + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "policy_id": { + "type": "keyword" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "upgrade_started_at": { + "type": "date" + }, + "upgraded_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "gis-map": { + "dynamic": "false", + "type": "object" + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } } + }, + "legacyIndexPatternRef": { + "index": false, + "type": "text" + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } + } + }, + "index-pattern": { + "dynamic": "false", + "properties": { + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "dynamic": "false", + "type": "object" + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_policies": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "config_yaml": { + "type": "text" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "compiled_input": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" } }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } } + }, + "policy_id": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_urls": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "dynamic": "false", + "type": "object" + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "bounds": { + "dynamic": "false", + "type": "object" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "dynamic": "false", + "type": "object" + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" + "keyword": { + "ignore_above": 256, + "type": "keyword" + } }, - "sourceFilters": { - "type": "text" + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } }, - "timeFieldName": { - "type": "keyword" + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } }, - "title": { - "type": "text" + "type": "text" + } + } + }, + "ml-job": { + "properties": { + "datafeed_id": { + "fields": { + "keyword": { + "type": "keyword" + } }, - "type": { - "type": "keyword" + "type": "text" + }, + "job_id": { + "fields": { + "keyword": { + "type": "keyword" + } }, - "typeMeta": { - "type": "keyword" + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } } } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" + } + }, + "monitoring-telemetry": { + "properties": { + "reportedClusterUuids": { + "type": "keyword" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "originId": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" } }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "index-pattern": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 - } + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "grid": { + "enabled": false, + "type": "object" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" } - }, - "space": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 - } + } + }, + "sort": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-session": { + "properties": { + "appId": { + "type": "keyword" + }, + "created": { + "type": "date" + }, + "expires": { + "type": "date" + }, + "idMapping": { + "enabled": false, + "type": "object" + }, + "initialState": { + "enabled": false, + "type": "object" + }, + "name": { + "type": "keyword" + }, + "restoreState": { + "enabled": false, + "type": "object" + }, + "sessionId": { + "type": "keyword" + }, + "status": { + "type": "keyword" + }, + "urlGeneratorId": { + "type": "keyword" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "security-solution-signals-migration": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "createdBy": { + "index": false, + "type": "text" + }, + "destinationIndex": { + "index": false, + "type": "keyword" + }, + "error": { + "index": false, + "type": "text" + }, + "sourceIndex": { + "type": "keyword" + }, + "status": { + "index": false, + "type": "keyword" + }, + "taskId": { + "index": false, + "type": "keyword" + }, + "updated": { + "index": false, + "type": "date" + }, + "updatedBy": { + "index": false, + "type": "text" + }, + "version": { + "type": "long" + } + } + }, + "server": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" } } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" } - }, - "namespace": { - "type": "keyword" - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } } + }, + "type": { + "type": "text" } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" } - } - }, - "server": { - "properties": { - "uuid": { - "type": "keyword" + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "disabledFeatures": { - "type": "keyword" - }, - "initials": { - "type": "keyword" - }, - "name": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 2048 + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" } } - } - }, - "spaceId": { - "type": "keyword" - }, - "telemetry": { - "properties": { - "enabled": { - "type": "boolean" + }, + "indexNames": { + "type": "text" + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "dynamic": "false", + "properties": { + "columnId": { + "type": "keyword" + }, + "columnType": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" } - }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" } }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" + "type": "text" + } + } + }, + "spaceId": { + "type": "keyword" + }, + "spaces-usage-stats": { + "dynamic": "false", + "type": "object" + }, + "tag": { + "properties": { + "color": { + "type": "text" + }, + "description": { + "type": "text" + }, + "name": { + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" } - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" + } + }, + "type": { + "type": "keyword" + }, + "ui-counter": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } }, - "createDate": { - "type": "date" + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } }, - "url": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 2048 + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } } } } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } } } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } } } - }, - "query": { - "properties": { - "title": { - "type": "text" - }, - "description": { - "type": "text" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "type": "keyword", - "index": false - } + } + }, + "uptime-dynamic-settings": { + "dynamic": "false", + "type": "object" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" } }, - "filters": { - "type": "object", - "enabled": false - }, - "timefilter": { - "type": "object", - "enabled": false + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } } + }, + "savedSearchRefName": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "index": false, + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "index": false, + "type": "text" } } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } } } -} +} \ No newline at end of file From 0b798f7d10dfd6d28bebd12e1aa8791fea2bf005 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 21 Jan 2021 14:29:23 +0100 Subject: [PATCH 46/83] make drag drop test more stable (#88614) --- test/functional/services/common/browser.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index b9f7ae8da4650..635fde6dad720 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -6,6 +6,7 @@ * Public License, v 1. */ +import { delay } from 'bluebird'; import { cloneDeepWith } from 'lodash'; import { Key, Origin } from 'selenium-webdriver'; // @ts-ignore internal modules are not typed @@ -298,11 +299,13 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { dispatchEvent(target, dropEvent, dragStartEvent.dataTransfer); const dragEndEvent = createEvent('dragend'); dispatchEvent(origin, dragEndEvent, dropEvent.dataTransfer); - }, 50); + }, 100); `, from, to ); + // wait for 150ms to make sure the script has run + await delay(150); } /** From f0be0ade196e9651cc419232846a430b7ff2c038 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 21 Jan 2021 08:33:49 -0500 Subject: [PATCH 47/83] [Uptime] Improve filter group (#88185) * Unskip "Observer location" test block. * Commit temp "describe.only" to make flaky test runner go faster. * Add optional chain for some potentially-null props. * Make overview filters type partial. * Repair broken types. * Remove only call from test. * Add unit tests and mark areas for improvement in \`FilterGroup\` component. * Add aria-label translations and new labels. * Refactor existing tests and add tests for new labels. * Fix bug in event handler and update tests. * Delete a comment. * Delete a comment. * Add some line breaks to help readability. * Add additional tests, fix a bug. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../filters_expression_select.test.tsx | 264 ++++++++++-------- .../filters_expression_select.tsx | 14 +- .../monitor_expressions/translations.ts | 21 ++ .../filter_popover.test.tsx.snap | 129 --------- .../filter_group/filter_group.test.tsx | 73 +++++ .../overview/filter_group/filter_group.tsx | 9 +- .../filter_group/filter_popover.test.tsx | 94 ++++--- .../overview/filter_group/filter_popover.tsx | 9 +- .../overview/filter_group/translations.tsx | 4 +- .../filter_group/uptime_filter_button.tsx | 5 + 10 files changed, 330 insertions(+), 292 deletions(-) delete mode 100644 x-pack/plugins/uptime/public/components/overview/filter_group/__snapshots__/filter_popover.test.tsx.snap create mode 100644 x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx index 3ebd828604463..21b1a067a4e9d 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx @@ -6,9 +6,18 @@ import React from 'react'; import { shallowWithIntl } from '@kbn/test/jest'; +import { fireEvent, waitFor } from '@testing-library/react'; import { FiltersExpressionsSelect } from './filters_expression_select'; +import { render } from '../../../../lib/helper/rtl_helpers'; +import { filterAriaLabels as aria } from './translations'; +import { filterLabels } from '../../filter_group/translations'; + +describe('FiltersExpressionSelect', () => { + const LOCATION_FIELD_NAME = 'observer.geo.name'; + const PORT_FIELD_NAME = 'url.port'; + const SCHEME_FIELD_NAME = 'monitor.type'; + const TAG_FIELD_NAME = 'tags'; -describe('filters expression select component', () => { it('is empty when no filters available', () => { const component = shallowWithIntl( { `); }); - it('contains provided new filter values', () => { - const component = shallowWithIntl( + it.each([ + [[LOCATION_FIELD_NAME], [aria.LOCATION], [aria.TAG, aria.PORT, aria.SCHEME]], + [ + [LOCATION_FIELD_NAME, TAG_FIELD_NAME], + [aria.LOCATION, aria.TAG], + [aria.PORT, aria.SCHEME], + ], + [ + [PORT_FIELD_NAME, SCHEME_FIELD_NAME], + [aria.PORT, aria.SCHEME], + [aria.LOCATION, aria.TAG], + ], + [[TAG_FIELD_NAME], [aria.TAG], [aria.LOCATION, aria.PORT, aria.SCHEME]], + ])('contains provided new filter values', (newFilters, expectedLabels, absentLabels) => { + const { getByLabelText, queryByLabelText } = render( { shouldUpdateUrl={false} /> ); - expect(component).toMatchInlineSnapshot(` - - - - - } - disabled={true} - fieldName="observer.geo.name" - forceOpen={false} - id="filter_location" - items={Array []} - loading={false} - onFilterFieldChange={[Function]} - selectedItems={Array []} - setForceOpen={[Function]} - title="Scheme" - /> - - - - - - - - - `); + expectedLabels.forEach((label) => expect(getByLabelText(label))); + absentLabels.forEach((label) => expect(queryByLabelText(label)).toBeNull()); }); - it('contains provided selected filter values', () => { - const component = shallowWithIntl( + it.each([ + ['Remove filter Location', LOCATION_FIELD_NAME], + ['Remove filter Scheme', SCHEME_FIELD_NAME], + ['Remove filter Port', PORT_FIELD_NAME], + ['Remove filter Tag', TAG_FIELD_NAME], + ])('fires remove filter handler', async (removeButtonLabel, expectedFieldName) => { + const onRemoveFilterMock = jest.fn(); + const setAlertParamsMock = jest.fn(); + const { getByLabelText } = render( ); - expect(component).toMatchInlineSnapshot(` - - - - - } - disabled={false} - fieldName="tags" - forceOpen={false} - id="filter_tags" - items={ - Array [ - "foo", - "bar", - ] - } - loading={false} - onFilterFieldChange={[Function]} - selectedItems={Array []} - setForceOpen={[Function]} - title="Tags" - /> - - - - - - - - - `); + + const removeButton = getByLabelText(removeButtonLabel); + fireEvent.click(removeButton); + expect(onRemoveFilterMock).toHaveBeenCalledTimes(1); + expect(onRemoveFilterMock).toHaveBeenCalledWith(expectedFieldName); + expect(setAlertParamsMock).toHaveBeenCalledTimes(1); + expect(setAlertParamsMock).toHaveBeenCalledWith('filters', { + [SCHEME_FIELD_NAME]: [], + [LOCATION_FIELD_NAME]: [], + [TAG_FIELD_NAME]: [], + [PORT_FIELD_NAME]: [], + }); }); + + const TEST_TAGS = ['foo', 'bar']; + const TEST_PORTS = [5601, 9200]; + const TEST_SCHEMES = ['http', 'tcp']; + const TEST_LOCATIONS = ['nyc', 'fairbanks']; + + it.each([ + [ + { + tags: TEST_TAGS, + ports: [5601, 9200], + schemes: ['http', 'tcp'], + locations: ['nyc', 'fairbanks'], + }, + [TAG_FIELD_NAME], + aria.TAG, + filterLabels.TAG, + TEST_TAGS, + ], + [ + { + tags: [], + ports: TEST_PORTS, + schemes: [], + locations: [], + }, + [PORT_FIELD_NAME], + aria.PORT, + filterLabels.PORT, + TEST_PORTS, + ], + [ + { + tags: [], + ports: [], + schemes: TEST_SCHEMES, + locations: [], + }, + [SCHEME_FIELD_NAME], + aria.SCHEME, + filterLabels.SCHEME, + TEST_SCHEMES, + ], + [ + { + tags: [], + ports: [], + schemes: [], + locations: TEST_LOCATIONS, + }, + [LOCATION_FIELD_NAME], + aria.LOCATION, + filterLabels.LOCATION, + TEST_LOCATIONS, + ], + ])( + 'applies accessible label to filter expressions, and contains selected filters', + /** + * @param filters the set of filters the test should supply as props + * @param newFilters the set of filter item types the component should render + * @param expectedFilterButtonAriaLabel the aria label for the popover button for the targeted filter + * @param filterLabel the name of the filter label expected in each item's aria-label + * @param expectedFilterItems the set of filter options the component should render + */ + async ( + filters, + newFilters, + expectedFilterButtonAriaLabel, + filterLabel, + expectedFilterItems + ) => { + const { getByLabelText } = render( + + ); + + const filterButton = getByLabelText(expectedFilterButtonAriaLabel); + + fireEvent.click(filterButton); + + await waitFor(() => { + expectedFilterItems.forEach((filterItem: string | number) => + expect(getByLabelText(`Filter by ${filterLabel} ${filterItem}.`)) + ); + }); + } + ); }); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx index 64862a8b748d8..63dcf31fd8111 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx @@ -8,7 +8,7 @@ import React, { useState } from 'react'; import { EuiButtonIcon, EuiExpression, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { FilterPopover } from '../../filter_group/filter_popover'; import { filterLabels } from '../../filter_group/translations'; -import { alertFilterLabels } from './translations'; +import { alertFilterLabels, filterAriaLabels } from './translations'; import { FilterExpressionsSelectProps } from './filters_expression_select_container'; import { OverviewFiltersState } from '../../../../state/reducers/overview_filters'; @@ -58,6 +58,7 @@ export const FiltersExpressionsSelect: React.FC = ({ const monitorFilters = [ { + 'aria-label': filterAriaLabels.PORT, onFilterFieldChange, loading: false, fieldName: 'url.port', @@ -71,6 +72,7 @@ export const FiltersExpressionsSelect: React.FC = ({ value: selectedPorts.length === 0 ? alertFilterLabels.ANY_PORT : selectedPorts?.join(','), }, { + 'aria-label': filterAriaLabels.TAG, onFilterFieldChange, loading: false, fieldName: 'tags', @@ -78,11 +80,12 @@ export const FiltersExpressionsSelect: React.FC = ({ disabled: tags?.length === 0, items: tags ?? [], selectedItems: selectedTags, - title: filterLabels.TAGS, + title: filterLabels.TAG, description: selectedTags.length === 0 ? alertFilterLabels.WITH : alertFilterLabels.WITH_TAG, value: selectedTags.length === 0 ? alertFilterLabels.ANY_TAG : selectedTags?.join(','), }, { + 'aria-label': filterAriaLabels.SCHEME, onFilterFieldChange, loading: false, fieldName: 'monitor.type', @@ -95,6 +98,7 @@ export const FiltersExpressionsSelect: React.FC = ({ value: selectedSchemes.length === 0 ? alertFilterLabels.ANY_TYPE : selectedSchemes?.join(','), }, { + 'aria-label': filterAriaLabels.LOCATION, onFilterFieldChange, loading: false, fieldName: 'observer.geo.name', @@ -102,7 +106,7 @@ export const FiltersExpressionsSelect: React.FC = ({ disabled: locations?.length === 0, items: locations ?? [], selectedItems: selectedLocations, - title: filterLabels.SCHEME, + title: filterLabels.LOCATION, description: selectedLocations.length === 0 ? alertFilterLabels.FROM : alertFilterLabels.FROM_LOCATION, value: @@ -132,7 +136,7 @@ export const FiltersExpressionsSelect: React.FC = ({ {...item} btnContent={ = ({ { diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/translations.ts b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/translations.ts index 64c082dc51334..919095d411fae 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/translations.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/translations.ts @@ -54,6 +54,12 @@ export const alertFilterLabels = { ANY_LOCATION: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.anyLocation', { defaultMessage: 'any location', }), + + REMOVE_FILTER_LABEL: (title: string) => + i18n.translate('xpack.uptime.alerts.monitorExpression.label', { + defaultMessage: 'Remove filter {title}', + values: { title }, + }), }; export const statusExpLabels = { @@ -82,3 +88,18 @@ export const timeExpLabels = { } ), }; + +export const filterAriaLabels = { + PORT: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.port.label', { + defaultMessage: `Select port filters to apply to the alert's query.`, + }), + TAG: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.tag.label', { + defaultMessage: `Select tag filters to apply to the alert's query.`, + }), + SCHEME: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.scheme.label', { + defaultMessage: `Select protocol scheme filters to apply to the alert's query.`, + }), + LOCATION: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.location.label', { + defaultMessage: `Select location filters to apply to the alert's query.`, + }), +}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/__snapshots__/filter_popover.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/filter_group/__snapshots__/filter_popover.test.tsx.snap deleted file mode 100644 index 47efe8946d0b6..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/__snapshots__/filter_popover.test.tsx.snap +++ /dev/null @@ -1,129 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FilterPopover component does not show item list when loading 1`] = ` - - } - closePopover={[Function]} - data-test-subj="filter-popover_test" - display="inlineBlock" - hasArrow={true} - id="test" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - zIndex={10000} -> - - - - -`; - -exports[`FilterPopover component renders without errors for valid props 1`] = ` - - } - closePopover={[Function]} - data-test-subj="filter-popover_test" - display="inlineBlock" - hasArrow={true} - id="test" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - zIndex={10000} -> - - - - -`; - -exports[`FilterPopover component returns selected items on popover close 1`] = ` -
-
- Some text -
-
-
- -
-
-
-`; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx new file mode 100644 index 0000000000000..0fc413011fa51 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.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 React from 'react'; +import { fireEvent, waitFor } from '@testing-library/react'; +import { render } from '../../../lib/helper/rtl_helpers'; +import { FilterGroupComponent } from './filter_group'; + +describe('FilterGroupComponent', () => { + const overviewFilters = { + locations: ['nyc', 'fairbanks'], + ports: [5601, 9200], + schemes: ['http', 'tcp'], + tags: ['prod', 'dev'], + }; + it.each([ + ['expands filter group for Location filter', 'Search for location'], + ['expands filter group for Port filter', 'Search for port'], + ['expands filter group for Scheme filter', 'Search for scheme'], + ['expands filter group for Tag filter', 'Search for tag'], + ])('handles loading', async (popoverButtonLabel, searchInputLabel) => { + const { getByLabelText } = render( + + ); + + const popoverButton = getByLabelText(popoverButtonLabel); + fireEvent.click(popoverButton); + await waitFor(() => { + const searchInput = getByLabelText(searchInputLabel); + expect(searchInput).toHaveAttribute('placeholder', 'Loading...'); + }); + }); + + it.each([ + [ + 'expands filter group for Location filter', + 'Search for location', + ['Filter by Location nyc.', 'Filter by Location fairbanks.'], + ], + [ + 'expands filter group for Port filter', + 'Search for port', + ['Filter by Port 5601.', 'Filter by Port 9200.'], + ], + [ + 'expands filter group for Scheme filter', + 'Search for scheme', + ['Filter by Scheme http.', 'Filter by Scheme tcp.'], + ], + [ + 'expands filter group for Tag filter', + 'Search for tag', + ['Filter by Tag prod.', 'Filter by Tag dev.'], + ], + ])( + 'displays filter items when clicked', + async (popoverButtonLabel, searchInputLabel, filterItemButtonLabels) => { + const { getByLabelText } = render( + + ); + + const popoverButton = getByLabelText(popoverButtonLabel); + fireEvent.click(popoverButton); + await waitFor(() => { + expect(getByLabelText(searchInputLabel)); + filterItemButtonLabels.forEach((itemLabel) => expect(getByLabelText(itemLabel))); + }); + } + ); +}); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx index a5256ee1e2626..a4a03ec5587a0 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx @@ -15,7 +15,7 @@ import { useFilterUpdate } from '../../../hooks/use_filter_update'; import { MONITOR_ROUTE } from '../../../../common/constants'; import { useSelectedFilters } from '../../../hooks/use_selected_filters'; -interface PresentationalComponentProps { +interface Props { loading: boolean; overviewFilters: OverviewFilters; } @@ -28,10 +28,7 @@ function isDisabled(array?: T[]) { return array ? array.length === 0 : true; } -export const FilterGroupComponent: React.FC = ({ - overviewFilters, - loading, -}) => { +export const FilterGroupComponent: React.FC = ({ overviewFilters, loading }) => { const { locations, ports, schemes, tags } = overviewFilters; const [updatedFieldValues, setUpdatedFieldValues] = useState<{ @@ -90,7 +87,7 @@ export const FilterGroupComponent: React.FC = ({ disabled: isDisabled(tags), items: tags ?? [], selectedItems: selectedTags, - title: filterLabels.TAGS, + title: filterLabels.TAG, }, ] : []), diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.test.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.test.tsx index d08d56dc2e2a0..dc45077901c10 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.test.tsx @@ -5,23 +5,22 @@ */ import React from 'react'; -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { fireEvent, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; import { FilterPopoverProps, FilterPopover } from './filter_popover'; -import { UptimeFilterButton } from './uptime_filter_button'; -import { EuiFilterSelectItem } from '@elastic/eui'; +import { render } from '../../../lib/helper/rtl_helpers'; describe('FilterPopover component', () => { let props: FilterPopoverProps; beforeEach(() => { props = { - fieldName: 'foo', + fieldName: 'test-fieldName', id: 'test', loading: false, items: ['first', 'second', 'third', 'fourth'], onFilterFieldChange: jest.fn(), selectedItems: ['first', 'third'], - title: 'bar', + title: 'test-title', }; }); @@ -29,43 +28,68 @@ describe('FilterPopover component', () => { jest.clearAllMocks(); }); - it('renders without errors for valid props', () => { - const wrapper = shallowWithIntl(); - expect(wrapper).toMatchSnapshot(); - }); - it('expands on button click', () => { - const wrapper = mountWithIntl(); + const { getByRole, getByLabelText, getByText, queryByLabelText, queryByText } = render( + + ); - // ensure the popover isn't open - expect(wrapper.find('EuiPopoverTitle')).toHaveLength(0); + const screenReaderOnlyText = 'You are in a dialog. To close this dialog, hit escape.'; - expect(wrapper.find(UptimeFilterButton)).toHaveLength(1); - wrapper.find(UptimeFilterButton).simulate('click'); + expect(queryByText(screenReaderOnlyText)).toBeNull(); + expect(queryByLabelText('Filter by bar fourth.')).toBeNull(); - // check that the popover is now open - expect(wrapper.find('EuiPopoverTitle')).toHaveLength(1); + fireEvent.click(getByRole('button')); + + expect(getByText(screenReaderOnlyText)); + expect(getByLabelText('Filter by test-title fourth.')); }); - it('does not show item list when loading', () => { + it('does not show item list when loading, and displays placeholder', async () => { props.loading = true; - const wrapper = shallowWithIntl(); - expect(wrapper).toMatchSnapshot(); - }); + const { getByRole, queryByText, getByLabelText } = render(); - it('returns selected items on popover close', () => { - const wrapper = mountWithIntl( -
-
Some text
- -
- ); - expect(wrapper.find(UptimeFilterButton)).toHaveLength(1); - wrapper.find(UptimeFilterButton).simulate('click'); - expect(wrapper.find(EuiFilterSelectItem)).toHaveLength(props.items.length); - wrapper.find(EuiFilterSelectItem).at(1).simulate('click'); - wrapper.find('#foo').simulate('click'); - const rendered = wrapper.render(); - expect(rendered).toMatchSnapshot(); + fireEvent.click(getByRole('button')); + + await waitFor(() => { + const search = getByLabelText('Search for test-title'); + expect(search).toHaveAttribute('placeholder', 'Loading...'); + }); + + expect(queryByText('Filter by test-title second.')).toBeNull(); }); + + it.each([ + [[], ['third'], ['third']], + [['first', 'third'], ['first'], ['third']], + [['fourth'], ['first', 'second'], ['first', 'second', 'fourth']], + ])( + 'returns selected items on popover close', + async (selectedPropsItems, expectedSelections, itemsToClick) => { + if (itemsToClick.length < 1) { + throw new Error('This test assumes at least one item will be clicked'); + } + props.selectedItems = selectedPropsItems; + + const { getByLabelText, queryByLabelText } = render(); + + const uptimeFilterButton = getByLabelText(`expands filter group for ${props.title} filter`); + + fireEvent.click(uptimeFilterButton); + + const generateLabelText = (item: string) => `Filter by ${props.title} ${item}.`; + + itemsToClick.forEach((item) => { + const optionButtonLabelText = generateLabelText(item); + const optionButton = getByLabelText(optionButtonLabelText); + fireEvent.click(optionButton); + }); + + fireEvent.click(uptimeFilterButton); + + await waitForElementToBeRemoved(() => queryByLabelText(generateLabelText(itemsToClick[0]))); + + expect(props.onFilterFieldChange).toHaveBeenCalledTimes(1); + expect(props.onFilterFieldChange).toHaveBeenCalledWith(props.fieldName, expectedSelections); + } + ); }); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx index e79c036d54e0e..d5b6efbf9ded7 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx @@ -76,8 +76,11 @@ export const FilterPopover = ({ numFilters={items.length} numActiveFilters={isOpen ? tempSelectedItems.length : selectedItems.length} onClick={() => { + if (isOpen) { + // only update these values on close + onFilterFieldChange(fieldName, tempSelectedItems); + } setIsOpen(!isOpen); - onFilterFieldChange(fieldName, tempSelectedItems); }} title={title} /> @@ -124,6 +127,10 @@ export const FilterPopover = ({ {!loading && itemsToDisplay.map((item) => ( ( Date: Thu, 21 Jan 2021 08:47:15 -0500 Subject: [PATCH 48/83] Remove plugin circular deps between `actions -> case` and `case -> securitySolution` (#88106) --- src/dev/run_find_plugins_with_circular_deps.ts | 2 -- .../actions/server/lib/ensure_sufficient_license.ts | 2 +- x-pack/plugins/case/common/api/connectors/mappings.ts | 8 +++++++- x-pack/plugins/case/server/connectors/case/index.ts | 3 +-- x-pack/plugins/case/server/connectors/index.ts | 1 - x-pack/plugins/case/server/index.ts | 1 - 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/dev/run_find_plugins_with_circular_deps.ts b/src/dev/run_find_plugins_with_circular_deps.ts index f86802f67c620..6e1667dbedfc6 100644 --- a/src/dev/run_find_plugins_with_circular_deps.ts +++ b/src/dev/run_find_plugins_with_circular_deps.ts @@ -20,8 +20,6 @@ interface Options { type CircularDepList = Set; const allowedList: CircularDepList = new Set([ - 'x-pack/plugins/actions -> x-pack/plugins/case', - 'x-pack/plugins/case -> x-pack/plugins/security_solution', 'x-pack/plugins/apm -> x-pack/plugins/infra', 'x-pack/plugins/lists -> x-pack/plugins/security_solution', 'x-pack/plugins/security -> x-pack/plugins/spaces', diff --git a/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts index c4ed47b3398df..c4a57bbf32c12 100644 --- a/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts +++ b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts @@ -8,7 +8,7 @@ import { LICENSE_TYPE } from '../../../licensing/common/types'; import { ServerLogActionTypeId, IndexActionTypeId } from '../builtin_action_types'; import { ActionTypeConfig, ActionTypeSecrets, ActionTypeParams } from '../types'; -export const CASE_ACTION_TYPE_ID = '.case'; +const CASE_ACTION_TYPE_ID = '.case'; const ACTIONS_SCOPED_WITHIN_STACK = new Set([ ServerLogActionTypeId, diff --git a/x-pack/plugins/case/common/api/connectors/mappings.ts b/x-pack/plugins/case/common/api/connectors/mappings.ts index b91f9d69e85e2..f8e9830fed7c1 100644 --- a/x-pack/plugins/case/common/api/connectors/mappings.ts +++ b/x-pack/plugins/case/common/api/connectors/mappings.ts @@ -7,7 +7,6 @@ /* eslint-disable @kbn/eslint/no-restricted-paths */ import * as rt from 'io-ts'; -import { ElasticUser } from '../../../../security_solution/public/cases/containers/types'; import { PushToServiceApiParams as JiraPushToServiceApiParams, Incident as JiraIncident, @@ -24,6 +23,13 @@ import { ResilientFieldsRT } from './resilient'; import { ServiceNowFieldsRT } from './servicenow'; import { JiraFieldsRT } from './jira'; +// Formerly imported from security_solution +export interface ElasticUser { + readonly email?: string | null; + readonly fullName?: string | null; + readonly username?: string | null; +} + export { JiraPushToServiceApiParams, ResilientPushToServiceApiParams, diff --git a/x-pack/plugins/case/server/connectors/case/index.ts b/x-pack/plugins/case/server/connectors/case/index.ts index 2195786f718ab..540f0b0a66728 100644 --- a/x-pack/plugins/case/server/connectors/case/index.ts +++ b/x-pack/plugins/case/server/connectors/case/index.ts @@ -23,7 +23,6 @@ import { GetActionTypeParams } from '..'; const supportedSubActions: string[] = ['create', 'update', 'addComment']; -export const CASE_ACTION_TYPE_ID = '.case'; // action type definition export function getActionType({ logger, @@ -34,7 +33,7 @@ export function getActionType({ alertsService, }: GetActionTypeParams): CaseActionType { return { - id: CASE_ACTION_TYPE_ID, + id: '.case', minimumLicenseRequired: 'basic', name: i18n.NAME, validate: { diff --git a/x-pack/plugins/case/server/connectors/index.ts b/x-pack/plugins/case/server/connectors/index.ts index 7fd09e61f2144..1a1e8ce718b07 100644 --- a/x-pack/plugins/case/server/connectors/index.ts +++ b/x-pack/plugins/case/server/connectors/index.ts @@ -21,7 +21,6 @@ import { } from '../services'; import { getActionType as getCaseConnector } from './case'; -export { CASE_ACTION_TYPE_ID } from './case'; export interface GetActionTypeParams { logger: Logger; diff --git a/x-pack/plugins/case/server/index.ts b/x-pack/plugins/case/server/index.ts index 19f3a15396729..91b8cdc33a0e3 100644 --- a/x-pack/plugins/case/server/index.ts +++ b/x-pack/plugins/case/server/index.ts @@ -8,7 +8,6 @@ import { PluginInitializerContext } from 'kibana/server'; import { ConfigSchema } from './config'; import { CasePlugin } from './plugin'; -export { CASE_ACTION_TYPE_ID } from './connectors'; export { CaseRequestContext } from './types'; export const config = { schema: ConfigSchema }; export const plugin = (initializerContext: PluginInitializerContext) => From b3bec0d6eff5a30f7819e3b7cb803d4baecf9de1 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 21 Jan 2021 14:03:26 +0000 Subject: [PATCH 49/83] [Task Manager] Cleans up polling shift mechanism (#88210) Cleanup work 1. Replaced naive initialisation of `last_polling_delay` 2. Changes values in `delayOnClaimConflicts` unit tests to make the values less confusing (it was easy to misunderstand the worker count for being the percentage of workers 3. Added comment explaining the usage of modulo --- .../monitoring/task_run_statistics.test.ts | 8 ++++++- .../server/monitoring/task_run_statistics.ts | 21 ++++++------------ .../polling/delay_on_claim_conflicts.test.ts | 22 +++++++++---------- .../server/polling/task_poller.ts | 5 +++-- .../task_manager/server/polling_lifecycle.ts | 5 +---- 5 files changed, 29 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts index 21ea72cbbb00d..625776db3250d 100644 --- a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.test.ts @@ -11,7 +11,12 @@ import sinon from 'sinon'; import { take, tap, bufferCount, skip, map } from 'rxjs/operators'; import { ConcreteTaskInstance, TaskStatus } from '../task'; -import { asTaskRunEvent, asTaskPollingCycleEvent, TaskTiming } from '../task_events'; +import { + asTaskRunEvent, + asTaskPollingCycleEvent, + TaskTiming, + asTaskManagerStatEvent, +} from '../task_events'; import { asOk } from '../lib/result_type'; import { TaskLifecycleEvent } from '../polling_lifecycle'; import { TaskRunResult } from '../task_running'; @@ -530,6 +535,7 @@ describe('Task Run Statistics', () => { events$.next( asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed, timing })) ); + events$.next(asTaskManagerStatEvent('pollingDelay', asOk(0))); events$.next( asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed, timing })) ); diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts index b881759d9103e..82fe0ec813435 100644 --- a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineLatest, merge, Observable, of } from 'rxjs'; +import { combineLatest, Observable } from 'rxjs'; import { filter, startWith, map } from 'rxjs/operators'; import { JsonObject } from 'src/plugins/kibana_utils/common'; import { isNumber, mapValues } from 'lodash'; @@ -160,19 +160,12 @@ export function createTaskRunAggregator( }) ), // get DateTime of latest polling delay refresh - merge( - /** - * as `combineLatest` hangs until it has its first value and we're not likely to reconfigure the delay in normal deployments, we needed some initial value. - I've used _now_ (`new Date().toISOString()`) as it made the most sense (it would be the time Kibana started), but it _could_ be confusing in the future. - */ - of(new Date().toISOString()), - taskPollingLifecycle.events.pipe( - filter( - (taskEvent: TaskLifecycleEvent) => - isTaskManagerStatEvent(taskEvent) && taskEvent.id === 'pollingDelay' - ), - map(() => new Date().toISOString()) - ) + taskPollingLifecycle.events.pipe( + filter( + (taskEvent: TaskLifecycleEvent) => + isTaskManagerStatEvent(taskEvent) && taskEvent.id === 'pollingDelay' + ), + map(() => new Date().toISOString()) ), ]).pipe( map(([{ polling }, pollingDelay]) => ({ diff --git a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts index 9f0eeedf05884..6b57f3470aecc 100644 --- a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts +++ b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts @@ -20,9 +20,9 @@ describe('delayOnClaimConflicts', () => { test( 'initializes with a delay of 0', - fakeSchedulers(async (advance) => { + fakeSchedulers(async () => { const pollInterval = 100; - const maxWorkers = 100; + const maxWorkers = 10; const taskLifecycleEvents$ = new Subject(); const delays = delayOnClaimConflicts( of(maxWorkers), @@ -40,9 +40,9 @@ describe('delayOnClaimConflicts', () => { test( 'emits a random delay whenever p50 of claim clashes exceed 80% of available max_workers', - fakeSchedulers(async (advance) => { + fakeSchedulers(async () => { const pollInterval = 100; - const maxWorkers = 100; + const maxWorkers = 10; const taskLifecycleEvents$ = new Subject(); const delays$ = delayOnClaimConflicts( @@ -61,7 +61,7 @@ describe('delayOnClaimConflicts', () => { result: FillPoolResult.PoolFilled, stats: { tasksUpdated: 0, - tasksConflicted: 80, + tasksConflicted: 8, tasksClaimed: 0, }, docs: [], @@ -80,9 +80,9 @@ describe('delayOnClaimConflicts', () => { test( 'doesnt emit a new delay when conflicts have reduced', - fakeSchedulers(async (advance) => { + fakeSchedulers(async () => { const pollInterval = 100; - const maxWorkers = 100; + const maxWorkers = 10; const taskLifecycleEvents$ = new Subject(); const handler = jest.fn(); @@ -104,7 +104,7 @@ describe('delayOnClaimConflicts', () => { result: FillPoolResult.PoolFilled, stats: { tasksUpdated: 0, - tasksConflicted: 80, + tasksConflicted: 8, tasksClaimed: 0, }, docs: [], @@ -124,7 +124,7 @@ describe('delayOnClaimConflicts', () => { result: FillPoolResult.PoolFilled, stats: { tasksUpdated: 0, - tasksConflicted: 70, + tasksConflicted: 7, tasksClaimed: 0, }, docs: [], @@ -135,14 +135,14 @@ describe('delayOnClaimConflicts', () => { await sleep(0); expect(handler.mock.calls.length).toEqual(2); - // shift average back up to threshold (70 + 90) / 2 = 80 + // shift average back up to threshold (7 + 9) / 2 = 80% of maxWorkers taskLifecycleEvents$.next( asTaskPollingCycleEvent( asOk({ result: FillPoolResult.PoolFilled, stats: { tasksUpdated: 0, - tasksConflicted: 90, + tasksConflicted: 9, tasksClaimed: 0, }, docs: [], diff --git a/x-pack/plugins/task_manager/server/polling/task_poller.ts b/x-pack/plugins/task_manager/server/polling/task_poller.ts index fac0137f38ba5..076dc8306cd91 100644 --- a/x-pack/plugins/task_manager/server/polling/task_poller.ts +++ b/x-pack/plugins/task_manager/server/polling/task_poller.ts @@ -84,8 +84,9 @@ export function createTaskPoller({ }) ), ]).pipe( - // pollDelay can only shift `timer` at the scale of `period`, so we round - // the delay to modulo the interval period + // We don't have control over `pollDelay` in the poller, and a change to `delayOnClaimConflicts` could accidentally cause us to pause Task Manager + // polling for a far longer duration that we intended. + // Since the goal is to shift it within the range of `period`, we use modulo as a safe guard to ensure this doesn't happen. switchMap(([period, pollDelay]) => timer(period + (pollDelay % period), period)), mapTo(none) ) diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts index 1133d1c269ca1..d698686a21664 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts @@ -129,10 +129,7 @@ export class TaskPollingLifecycle { this.events$, config.version_conflict_threshold, config.monitored_stats_running_average_window - ); - pollIntervalDelay$.subscribe((delay) => { - emitEvent(asTaskManagerStatEvent('pollingDelay', asOk(delay))); - }); + ).pipe(tap((delay) => emitEvent(asTaskManagerStatEvent('pollingDelay', asOk(delay))))); // the task poller that polls for work on fixed intervals and on demand const poller$: Observable< From c89f1f18d392e9439a9fb17080ed87d3157a3d55 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 21 Jan 2021 14:04:42 +0000 Subject: [PATCH 50/83] [Task Manager] Increment task `attempts` when they fail during markTaskAsRunning (#88669) When something causes an exception in `TaskRunner.markTaskAsRunning()` its execution fails, but this happens before we update the SO, which means that this failure does not count towards the `attempts` on the task. Task Manager will continue to try running this task for ever. This PR increments the `attempts` when a failure occurs during `TaskRunner.markTaskAsRunning()` to ensure such a task doesn't continue to run to infinity. Note that this fix will not affect `scheduled` tasks, as they are designed to _ignore_ their `attempts` and run for ever. In such a case this task will continue to consume Task Manager resources until canceled, but these failures will be logged and could be identified when needed. --- .../server/task_running/task_runner.test.ts | 151 +++++++++++++++--- .../server/task_running/task_runner.ts | 52 ++++-- .../sample_task_plugin/server/plugin.ts | 3 + .../task_manager/task_management.ts | 15 ++ 4 files changed, 191 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts index 77434d2b6559c..42cc71f759bfb 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts @@ -666,9 +666,7 @@ describe('TaskManagerRunner', () => { store.update = sinon .stub() - .throws( - SavedObjectsErrorHelpers.decorateConflictError(new Error('repo error')).output.payload - ); + .throws(SavedObjectsErrorHelpers.decorateConflictError(new Error('repo error'))); expect(await runner.markTaskAsRunning()).toEqual(false); }); @@ -699,15 +697,126 @@ describe('TaskManagerRunner', () => { store.update = sinon .stub() - .throws(SavedObjectsErrorHelpers.createGenericNotFoundError('type', 'id').output.payload); - - return expect(runner.markTaskAsRunning()).rejects.toMatchInlineSnapshot(` - Object { - "error": "Not Found", - "message": "Saved object [type/id] not found", - "statusCode": 404, - } - `); + .throws(SavedObjectsErrorHelpers.createGenericNotFoundError('type', 'id')); + + return expect(runner.markTaskAsRunning()).rejects.toMatchInlineSnapshot( + `[Error: Saved object [type/id] not found]` + ); + }); + + test(`it tries to increment a task's attempts when markTaskAsRunning fails for unexpected reasons`, async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(1, 3); + const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); + const timeoutMinutes = 1; + const getRetryStub = sinon.stub().returns(nextRetry); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: undefined, + }, + definitions: { + bar: { + title: 'Bar!', + timeout: `${timeoutMinutes}m`, + getRetry: getRetryStub, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + store.update = sinon.stub(); + store.update.onFirstCall().throws(SavedObjectsErrorHelpers.createBadRequestError('type')); + store.update.onSecondCall().resolves(); + + await expect(runner.markTaskAsRunning()).rejects.toMatchInlineSnapshot( + `[Error: type: Bad Request]` + ); + + sinon.assert.calledWith(store.update, { + ...mockInstance({ + id, + attempts: initialAttempts + 1, + schedule: undefined, + }), + status: TaskStatus.Idle, + startedAt: null, + retryAt: null, + ownerId: null, + }); + }); + + test(`it doesnt try to increment a task's attempts when markTaskAsRunning fails for version conflict`, async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(1, 3); + const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); + const timeoutMinutes = 1; + const getRetryStub = sinon.stub().returns(nextRetry); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: undefined, + }, + definitions: { + bar: { + title: 'Bar!', + timeout: `${timeoutMinutes}m`, + getRetry: getRetryStub, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + store.update = sinon.stub(); + store.update.onFirstCall().throws(SavedObjectsErrorHelpers.createConflictError('type', 'id')); + store.update.onSecondCall().resolves(); + + await expect(runner.markTaskAsRunning()).resolves.toMatchInlineSnapshot(`false`); + + sinon.assert.calledOnce(store.update); + }); + + test(`it doesnt try to increment a task's attempts when markTaskAsRunning fails due to Saved Object not being found`, async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(1, 3); + const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); + const timeoutMinutes = 1; + const getRetryStub = sinon.stub().returns(nextRetry); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: undefined, + }, + definitions: { + bar: { + title: 'Bar!', + timeout: `${timeoutMinutes}m`, + getRetry: getRetryStub, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + store.update = sinon.stub(); + store.update + .onFirstCall() + .throws(SavedObjectsErrorHelpers.createGenericNotFoundError('type', 'id')); + store.update.onSecondCall().resolves(); + + await expect(runner.markTaskAsRunning()).rejects.toMatchInlineSnapshot( + `[Error: Saved object [type/id] not found]` + ); + + sinon.assert.calledOnce(store.update); }); test('uses getRetry (returning true) to set retryAt when defined', async () => { @@ -1114,12 +1223,8 @@ describe('TaskManagerRunner', () => { }; } - function testOpts(opts: TestOpts) { - const callCluster = sinon.stub(); - const createTaskRunner = sinon.stub(); - const logger = mockLogger(); - - const instance = Object.assign( + function mockInstance(instance: Partial = {}) { + return Object.assign( { id: 'foo', taskType: 'bar', @@ -1137,8 +1242,16 @@ describe('TaskManagerRunner', () => { user: 'example', ownerId: null, }, - opts.instance || {} + instance ); + } + + function testOpts(opts: TestOpts) { + const callCluster = sinon.stub(); + const createTaskRunner = sinon.stub(); + const logger = mockLogger(); + + const instance = mockInstance(opts.instance); const store = { update: sinon.stub(), diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index 704386d88ea3a..e4e2595d44516 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -10,13 +10,23 @@ * rescheduling, middleware application, etc. */ -import { Logger } from 'src/core/server'; import apm from 'elastic-apm-node'; import { performance } from 'perf_hooks'; import { identity, defaults, flow } from 'lodash'; +import { Logger, SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import { Middleware } from '../lib/middleware'; -import { asOk, asErr, mapErr, eitherAsync, unwrap, isOk, mapOk, Result } from '../lib/result_type'; +import { + asOk, + asErr, + mapErr, + eitherAsync, + unwrap, + isOk, + mapOk, + Result, + promiseResult, +} from '../lib/result_type'; import { TaskRun, TaskMarkRunning, @@ -226,17 +236,15 @@ export class TaskManagerRunner implements TaskRunner { 'taskManager' ); - const VERSION_CONFLICT_STATUS = 409; const now = new Date(); + try { + const { taskInstance } = await this.beforeMarkRunning({ + taskInstance: this.instance, + }); - const { taskInstance } = await this.beforeMarkRunning({ - taskInstance: this.instance, - }); - - const attempts = taskInstance.attempts + 1; - const ownershipClaimedUntil = taskInstance.retryAt; + const attempts = taskInstance.attempts + 1; + const ownershipClaimedUntil = taskInstance.retryAt; - try { const { id } = taskInstance; const timeUntilClaimExpires = howManyMsUntilOwnershipClaimExpires(ownershipClaimedUntil); @@ -284,7 +292,16 @@ export class TaskManagerRunner implements TaskRunner { if (apmTrans) apmTrans.end('failure'); performanceStopMarkingTaskAsRunning(); this.onTaskEvent(asTaskMarkRunningEvent(this.id, asErr(error))); - if (error.statusCode !== VERSION_CONFLICT_STATUS) { + if (!SavedObjectsErrorHelpers.isConflictError(error)) { + if (!SavedObjectsErrorHelpers.isNotFoundError(error)) { + // try to release claim as an unknown failure prevented us from marking as running + mapErr((errReleaseClaim: Error) => { + this.logger.error( + `[Task Runner] Task ${this.instance.id} failed to release claim after failure: ${errReleaseClaim}` + ); + }, await this.releaseClaimAndIncrementAttempts()); + } + throw error; } } @@ -314,6 +331,19 @@ export class TaskManagerRunner implements TaskRunner { : asOk(result || EMPTY_RUN_RESULT); } + private async releaseClaimAndIncrementAttempts(): Promise> { + return promiseResult( + this.bufferedTaskStore.update({ + ...this.instance, + status: TaskStatus.Idle, + attempts: this.instance.attempts + 1, + startedAt: null, + retryAt: null, + ownerId: null, + }) + ); + } + private shouldTryToScheduleRetry(): boolean { if (this.instance.schedule) { return true; diff --git a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts index 0326adb90775a..bdb32c7eab757 100644 --- a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts +++ b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts @@ -167,6 +167,9 @@ export class SampleTaskManagerFixturePlugin }, async beforeMarkRunning(context) { + if (context.taskInstance?.params?.originalParams?.throwOnMarkAsRunning) { + throw new Error(`Sample task ${context.taskInstance.id} threw on MarkAsRunning`); + } return context; }, }); diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts index 7f4585fad4729..d94efa2ae10bc 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts @@ -524,6 +524,21 @@ export default function ({ getService }: FtrProviderContext) { }); }); + it('should increment attempts when task fails on markAsRunning', async () => { + const originalTask = await scheduleTask({ + taskType: 'sampleTask', + params: { throwOnMarkAsRunning: true }, + }); + + await delay(DEFAULT_POLL_INTERVAL * 3); + + await retry.try(async () => { + const task = await currentTask(originalTask.id); + expect(task.attempts).to.eql(3); + expect(task.status).to.eql('failed'); + }); + }); + it('should return a task run error result when trying to run a non-existent task', async () => { // runNow should fail const failedRunNowResult = await runTaskNow({ From d3fa06b268c14cb03a48e1548e50b87d6eb3220f Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Thu, 21 Jan 2021 09:06:30 -0500 Subject: [PATCH 51/83] updates doc on action parameter variable mustache escaping (#88521) Provides more detail on mustache variable escaping within action parameter templates. --- docs/user/alerting/defining-alerts.asciidoc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/user/alerting/defining-alerts.asciidoc b/docs/user/alerting/defining-alerts.asciidoc index 94cca7f91494e..ed0e2966b2288 100644 --- a/docs/user/alerting/defining-alerts.asciidoc +++ b/docs/user/alerting/defining-alerts.asciidoc @@ -59,11 +59,19 @@ Each action type exposes different properties. For example an email action allow [role="screenshot"] image::images/alert-flyout-action-details.png[UI for defining an email action] -Using the https://mustache.github.io/[Mustache] template syntax `{{variable name}}`, you can pass alert values at the time a condition is detected to an action. Note that using two curly braces will escape any HTML. Should you need to preserve HTML, use three curly braces (`{{{`). Available variables differ by alert type, and a list can be accessed using the "add variable" button. +Using the https://mustache.github.io/[Mustache] template syntax `{{variable name}}`, you can pass alert values at the time a condition is detected to an action. Available variables differ by alert type, and the list of available variables can be accessed using the "add variable" button. [role="screenshot"] image::images/alert-flyout-action-variables.png[Passing alert values to an action] +Some cases exist where the variable values will be "escaped", when used in a context where escaping is needed: + +- For the <> connector, the `message` action configuration property escapes any characters that would be interpreted as Markdown. +- For the <> connector, the `message` action configuration property escapes any characters that would be interpreted as Slack Markdown. +- For the <> connector, the `body` action configuration property escapes any characters that are invalid in JSON string values. + +Mustache also supports "triple braces" of the form `{{{variable name}}}`, which indicates no escaping should be done at all. Care should be used when using this form, as it could end up rendering the variable content in such a way as to make the resulting parameter invalid or formatted incorrectly. + You can attach more than one action. Clicking the "Add action" button will prompt you to select another alert type and repeat the above steps again. [role="screenshot"] From b3a9754394ec983fdcdc70915414d377f99717b8 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 21 Jan 2021 15:20:22 +0100 Subject: [PATCH 52/83] [Core] Explicit typings for request handler context (#88718) * move context to server part. couple with RequestHandlerContext Context implementation will be simplified in follow-up. * adopt core code * adopt bfetch code * adopt data code * adopt search examples * adopt vis_type_timelion * adopt vis_type_timeseries * adopt plugin functional tests * adopt actions * adopt alerting plugin * adopt APM plugin * adopt beats_management * adopt case plugin * adopt cross_cluster_replication * adopt data_enhanced * adopt event_log * adopt global_search * adopt index_management * adopt infra * adopt licensing * adopt lists * adopt logstash * adopt reporting * adopt observability * adopt monitoring * adopt rollup * adopt so tagging * adopt security * adopt security_solutions * adopt watcher * adopt uptime * adopt spaces * adopt snapshot_restore * adopt features changes * mute error when null used to extend context * update docs * small cleanup * add type safety for return type * refactor registerRouteHandlerContext type * update docs * update license header * update docs * fix type error. fetch body does not accept array of strings * fix telemetry test * remove unnecessary ts-ignore * address comments * update docs --- .../architecture/core/index.asciidoc | 20 +- ...kibana-plugin-core-server.httpresources.md | 2 +- ...ugin-core-server.httpresources.register.md | 2 +- ...core-server.httpresourcesrequesthandler.md | 2 +- ...re-server.httpservicesetup.createrouter.md | 2 +- ...ana-plugin-core-server.httpservicesetup.md | 4 +- ...ervicesetup.registerroutehandlercontext.md | 9 +- ...na-plugin-core-server.icontextcontainer.md | 2 +- ...erver.icontextcontainer.registercontext.md | 6 +- ...ana-plugin-core-server.icontextprovider.md | 2 +- ...ibana-plugin-core-server.irouter.delete.md | 2 +- .../kibana-plugin-core-server.irouter.get.md | 2 +- .../kibana-plugin-core-server.irouter.md | 12 +- ...kibana-plugin-core-server.irouter.patch.md | 2 +- .../kibana-plugin-core-server.irouter.post.md | 2 +- .../kibana-plugin-core-server.irouter.put.md | 2 +- ...ibana-plugin-core-server.requesthandler.md | 2 +- ...e-server.requesthandlercontextcontainer.md | 2 +- ...re-server.requesthandlercontextprovider.md | 2 +- ...lugin-core-server.requesthandlerwrapper.md | 2 +- ...ibana-plugin-core-server.routeregistrar.md | 2 +- ...gins-data-public.searchsource.serialize.md | 4 +- ...ata-server.dataapirequesthandlercontext.md | 18 + ...er.dataapirequesthandlercontext.session.md | 11 + .../kibana-plugin-plugins-data-server.md | 1 + ...plugin-plugins-data-server.plugin.start.md | 8 +- examples/search_examples/server/plugin.ts | 13 +- .../server/routes/register_routes.ts | 10 +- .../server/routes/server_search_route.ts | 9 +- .../context/container}/context.mock.ts | 1 + .../server/context/container/context.test.ts | 319 ++++++++++++++++++ .../context/container}/context.ts | 63 ++-- src/core/server/context/container/index.ts | 9 + .../server/context/context_service.mock.ts | 2 +- .../context/context_service.test.mocks.ts | 4 +- src/core/server/context/context_service.ts | 2 +- src/core/server/context/index.ts | 2 +- src/core/server/http/http_service.mock.ts | 2 + src/core/server/http/http_service.ts | 18 +- .../lifecycle_handlers.test.ts | 6 +- src/core/server/http/router/router.mock.ts | 2 +- src/core/server/http/router/router.ts | 56 +-- src/core/server/http/types.ts | 43 ++- .../http_resources/http_resources_service.ts | 6 +- src/core/server/http_resources/types.ts | 20 +- src/core/server/legacy/legacy_service.ts | 24 +- src/core/server/plugins/plugin_context.ts | 16 +- src/core/server/server.api.md | 41 ++- src/core/server/server.ts | 2 +- src/core/utils/context.test.ts | 268 --------------- src/core/utils/index.ts | 8 - src/plugins/bfetch/server/plugin.ts | 14 +- src/plugins/data/public/public.api.md | 2 +- src/plugins/data/server/index.ts | 2 + .../routes/util/handle_errors.ts | 14 +- .../data/server/search/routes/msearch.ts | 8 +- .../data/server/search/routes/search.ts | 4 +- .../data/server/search/search_service.ts | 24 +- src/plugins/data/server/search/types.ts | 19 +- src/plugins/data/server/server.api.md | 56 +-- .../routes/telemetry_opt_in_stats.test.ts | 2 +- .../server/routes/telemetry_opt_in_stats.ts | 3 +- .../vis_type_timelion/server/plugin.ts | 12 +- .../server/routes/validate_es.ts | 7 +- .../server/lib/get_fields.ts | 5 +- .../server/lib/get_vis_data.ts | 7 +- .../strategies/abstract_search_strategy.ts | 14 +- .../vis_type_timeseries/server/plugin.ts | 11 +- .../vis_type_timeseries/server/routes/vis.ts | 5 +- .../vis_type_timeseries/server/types.ts | 16 + .../plugins/core_plugin_a/server/index.ts | 2 +- .../plugins/core_plugin_a/server/plugin.ts | 25 +- .../plugins/core_plugin_b/server/plugin.ts | 12 +- .../server/index.ts | 1 - .../server/plugin.ts | 10 - x-pack/plugins/actions/server/index.ts | 5 +- x-pack/plugins/actions/server/plugin.test.ts | 16 +- x-pack/plugins/actions/server/plugin.ts | 8 +- .../plugins/actions/server/routes/create.ts | 23 +- .../plugins/actions/server/routes/delete.ts | 22 +- .../plugins/actions/server/routes/execute.ts | 23 +- x-pack/plugins/actions/server/routes/get.ts | 22 +- .../plugins/actions/server/routes/get_all.ts | 20 +- .../server/routes/list_action_types.ts | 20 +- .../plugins/actions/server/routes/update.ts | 22 +- x-pack/plugins/actions/server/types.ts | 15 +- x-pack/plugins/alerts/server/index.ts | 3 +- x-pack/plugins/alerts/server/plugin.ts | 11 +- .../server/routes/_mock_handler_arguments.ts | 16 +- .../plugins/alerts/server/routes/aggregate.ts | 18 +- x-pack/plugins/alerts/server/routes/create.ts | 18 +- x-pack/plugins/alerts/server/routes/delete.ts | 18 +- .../plugins/alerts/server/routes/disable.ts | 18 +- x-pack/plugins/alerts/server/routes/enable.ts | 18 +- x-pack/plugins/alerts/server/routes/find.ts | 19 +- x-pack/plugins/alerts/server/routes/get.ts | 18 +- .../routes/get_alert_instance_summary.ts | 21 +- .../alerts/server/routes/get_alert_state.ts | 18 +- x-pack/plugins/alerts/server/routes/health.ts | 16 +- .../alerts/server/routes/lib/error_handler.ts | 20 +- .../alerts/server/routes/list_alert_types.ts | 16 +- .../plugins/alerts/server/routes/mute_all.ts | 18 +- .../alerts/server/routes/mute_instance.ts | 18 +- .../alerts/server/routes/unmute_all.ts | 18 +- .../alerts/server/routes/unmute_instance.ts | 18 +- x-pack/plugins/alerts/server/routes/update.ts | 18 +- .../alerts/server/routes/update_api_key.ts | 18 +- x-pack/plugins/alerts/server/types.ts | 28 +- x-pack/plugins/apm/server/feature.ts | 4 +- x-pack/plugins/apm/server/plugin.ts | 6 +- .../apm/server/routes/create_api/index.ts | 6 +- x-pack/plugins/apm/server/routes/typings.ts | 10 +- .../beats_management/server/lib/types.ts | 19 +- .../plugins/beats_management/server/plugin.ts | 26 +- .../server/routes/beats/configuration.ts | 4 +- .../server/routes/beats/enroll.ts | 4 +- .../server/routes/beats/events.ts | 4 +- .../server/routes/beats/get.ts | 4 +- .../server/routes/beats/list.ts | 6 +- .../server/routes/beats/tag_assignment.ts | 4 +- .../server/routes/beats/tag_removal.ts | 6 +- .../server/routes/beats/update.ts | 6 +- .../server/routes/configurations/delete.ts | 6 +- .../server/routes/configurations/get.ts | 6 +- .../server/routes/configurations/upsert.ts | 6 +- .../beats_management/server/routes/index.ts | 5 +- .../server/routes/tags/assignable.ts | 4 +- .../server/routes/tags/delete.ts | 4 +- .../server/routes/tags/get.ts | 4 +- .../server/routes/tags/list.ts | 4 +- .../server/routes/tags/set.ts | 4 +- .../server/routes/tokens/create.ts | 4 +- .../server/routes/wrap_route_with_security.ts | 22 +- .../plugins/case/server/client/index.test.ts | 8 +- x-pack/plugins/case/server/client/index.ts | 4 - x-pack/plugins/case/server/client/mocks.ts | 5 +- x-pack/plugins/case/server/client/types.ts | 8 +- .../case/server/connectors/case/index.ts | 5 +- x-pack/plugins/case/server/plugin.ts | 18 +- .../routes/api/__fixtures__/route_contexts.ts | 5 +- .../cases/configure/get_connectors.test.ts | 1 + .../configure/post_push_to_service.test.ts | 11 +- .../plugins/case/server/routes/api/types.ts | 7 +- x-pack/plugins/case/server/types.ts | 26 +- .../server/plugin.ts | 28 +- .../follower_index/register_update_route.ts | 2 +- .../server/services/license.ts | 12 +- .../cross_cluster_replication/server/types.ts | 18 +- x-pack/plugins/data_enhanced/server/plugin.ts | 3 +- .../data_enhanced/server/routes/mocks.ts | 4 +- .../server/routes/session.test.ts | 10 +- .../data_enhanced/server/routes/session.ts | 5 +- x-pack/plugins/data_enhanced/server/type.ts | 18 + x-pack/plugins/event_log/server/plugin.ts | 13 +- .../plugins/event_log/server/routes/find.ts | 10 +- .../event_log/server/routes/find_by_ids.ts | 9 +- x-pack/plugins/event_log/server/types.ts | 29 +- .../plugins/features/server/routes/index.ts | 4 +- x-pack/plugins/features/server/types.ts | 19 ++ x-pack/plugins/global_search/server/mocks.ts | 14 + x-pack/plugins/global_search/server/plugin.ts | 23 +- .../global_search/server/routes/find.ts | 4 +- .../server/routes/get_searchable_types.ts | 4 +- .../global_search/server/routes/index.ts | 4 +- .../routes/integration_tests/find.test.ts | 11 +- .../get_searchable_types.test.ts | 15 +- x-pack/plugins/global_search/server/types.ts | 15 +- .../plugins/index_management/server/plugin.ts | 31 +- .../server/services/license.ts | 14 +- .../plugins/index_management/server/types.ts | 32 +- .../lib/adapters/fields/adapter_types.ts | 4 +- .../fields/framework_fields_adapter.ts | 4 +- .../framework/kibana_framework_adapter.ts | 34 +- .../log_entries/kibana_log_entries_adapter.ts | 6 +- .../lib/adapters/metrics/adapter_types.ts | 5 +- .../metrics/kibana_metrics_adapter.ts | 7 +- .../elasticsearch_source_status_adapter.ts | 8 +- .../log_threshold_chart_preview.ts | 8 +- .../infra/server/lib/create_search_client.ts | 4 +- .../infra/server/lib/domains/fields_domain.ts | 4 +- .../log_entries_domain/log_entries_domain.ts | 17 +- .../server/lib/domains/metrics_domain.ts | 5 +- .../lib/infra_ml/metrics_hosts_anomalies.ts | 4 +- .../lib/infra_ml/metrics_k8s_anomalies.ts | 4 +- .../lib/log_analysis/log_entry_anomalies.ts | 9 +- .../plugins/infra/server/lib/source_status.ts | 23 +- x-pack/plugins/infra/server/plugin.ts | 6 +- .../lib/get_cloud_metadata.ts | 4 +- .../metadata/lib/get_cloud_metric_metadata.ts | 4 +- .../metadata/lib/get_metric_metadata.ts | 4 +- .../routes/metadata/lib/get_node_info.ts | 4 +- .../routes/metadata/lib/get_pod_node_name.ts | 4 +- x-pack/plugins/infra/server/types.ts | 13 +- .../server/utils/calculate_metric_interval.ts | 1 - .../server/licensing_route_handler_context.ts | 8 +- x-pack/plugins/licensing/server/mocks.ts | 6 +- .../licensing/server/routes/feature_usage.ts | 5 +- .../plugins/licensing/server/routes/index.ts | 5 +- .../plugins/licensing/server/routes/info.ts | 4 +- .../routes/internal/notify_feature_usage.ts | 4 +- .../routes/internal/register_feature.ts | 4 +- x-pack/plugins/licensing/server/types.ts | 18 +- .../server/wrap_route_with_license_check.ts | 17 +- x-pack/plugins/lists/server/index.ts | 2 +- x-pack/plugins/lists/server/plugin.ts | 8 +- .../routes/create_endpoint_list_item_route.ts | 5 +- .../routes/create_endpoint_list_route.ts | 5 +- .../create_exception_list_item_route.ts | 5 +- .../routes/create_exception_list_route.ts | 5 +- .../server/routes/create_list_index_route.ts | 5 +- .../server/routes/create_list_item_route.ts | 5 +- .../lists/server/routes/create_list_route.ts | 5 +- .../routes/delete_endpoint_list_item_route.ts | 5 +- .../delete_exception_list_item_route.ts | 5 +- .../routes/delete_exception_list_route.ts | 5 +- .../server/routes/delete_list_index_route.ts | 5 +- .../server/routes/delete_list_item_route.ts | 5 +- .../lists/server/routes/delete_list_route.ts | 5 +- .../routes/export_exception_list_route.ts | 5 +- .../server/routes/export_list_item_route.ts | 5 +- .../routes/find_endpoint_list_item_route.ts | 5 +- .../routes/find_exception_list_item_route.ts | 5 +- .../routes/find_exception_list_route.ts | 5 +- .../server/routes/find_list_item_route.ts | 5 +- .../lists/server/routes/find_list_route.ts | 5 +- .../server/routes/import_list_item_route.ts | 4 +- .../lists/server/routes/init_routes.ts | 5 +- .../server/routes/patch_list_item_route.ts | 5 +- .../lists/server/routes/patch_list_route.ts | 5 +- .../routes/read_endpoint_list_item_route.ts | 5 +- .../routes/read_exception_list_item_route.ts | 5 +- .../routes/read_exception_list_route.ts | 5 +- .../server/routes/read_list_index_route.ts | 5 +- .../server/routes/read_list_item_route.ts | 5 +- .../lists/server/routes/read_list_route.ts | 5 +- .../server/routes/read_privileges_route.ts | 4 +- .../routes/update_endpoint_list_item_route.ts | 5 +- .../update_exception_list_item_route.ts | 5 +- .../routes/update_exception_list_route.ts | 5 +- .../server/routes/update_list_item_route.ts | 5 +- .../lists/server/routes/update_list_route.ts | 5 +- .../routes/utils/get_exception_list_client.ts | 7 +- .../server/routes/utils/get_list_client.ts | 5 +- x-pack/plugins/lists/server/types.ts | 34 +- x-pack/plugins/logstash/server/plugin.ts | 10 +- .../logstash/server/routes/cluster/load.ts | 4 +- .../plugins/logstash/server/routes/index.ts | 4 +- .../logstash/server/routes/pipeline/delete.ts | 5 +- .../logstash/server/routes/pipeline/load.ts | 4 +- .../logstash/server/routes/pipeline/save.ts | 7 +- .../server/routes/pipelines/delete.ts | 7 +- .../logstash/server/routes/pipelines/list.ts | 5 +- x-pack/plugins/logstash/server/types.ts | 22 +- x-pack/plugins/monitoring/server/plugin.ts | 6 +- x-pack/plugins/monitoring/server/types.ts | 23 +- .../lib/annotations/bootstrap_annotations.ts | 13 +- .../annotations/register_annotation_apis.ts | 11 +- x-pack/plugins/observability/server/types.ts | 19 ++ x-pack/plugins/reporting/server/core.ts | 4 +- .../csv_from_savedobject/create_job.ts | 4 +- .../csv_from_savedobject/execute_job.ts | 5 +- .../reporting/server/lib/enqueue_job.ts | 7 +- x-pack/plugins/reporting/server/plugin.ts | 10 +- .../server/routes/diagnostic/browser.test.ts | 7 +- .../server/routes/diagnostic/config.test.ts | 7 +- .../routes/diagnostic/screenshot.test.ts | 7 +- .../server/routes/generation.test.ts | 7 +- .../reporting/server/routes/jobs.test.ts | 8 +- .../lib/authorized_user_pre_routing.test.ts | 5 +- .../routes/lib/authorized_user_pre_routing.ts | 12 +- .../reporting/server/routes/types.d.ts | 11 +- x-pack/plugins/reporting/server/types.ts | 16 +- x-pack/plugins/rollup/server/plugin.ts | 27 +- .../api/search/register_search_route.ts | 2 +- .../plugins/rollup/server/services/license.ts | 12 +- x-pack/plugins/rollup/server/types.ts | 23 +- .../saved_objects_tagging/server/plugin.ts | 8 +- .../assignments/find_assignable_objects.ts | 4 +- .../assignments/get_assignable_types.ts | 4 +- .../assignments/update_tags_assignments.ts | 4 +- .../server/routes/index.ts | 4 +- .../server/routes/internal/bulk_delete.ts | 4 +- .../server/routes/internal/find_tags.ts | 4 +- .../server/routes/tags/create_tag.ts | 4 +- .../server/routes/tags/delete_tag.ts | 4 +- .../server/routes/tags/get_all_tags.ts | 4 +- .../server/routes/tags/get_tag.ts | 4 +- .../server/routes/tags/update_tag.ts | 4 +- .../saved_objects_tagging/server/types.ts | 15 +- .../server/routes/api_keys/enabled.test.ts | 9 +- .../routes/authentication/common.test.ts | 17 +- .../server/routes/authentication/saml.test.ts | 5 +- .../authorization/privileges/get.test.ts | 5 +- .../share_saved_object_permissions.test.ts | 11 +- .../plugins/security/server/routes/index.ts | 5 +- .../server/routes/licensed_route_handler.ts | 6 +- .../routes/session_management/extend.test.ts | 9 +- .../routes/session_management/info.test.ts | 17 +- .../routes/users/change_password.test.ts | 9 +- .../routes/views/access_agreement.test.ts | 11 +- .../server/routes/views/capture_url.test.ts | 4 +- .../server/routes/views/login.test.ts | 6 +- x-pack/plugins/security/server/types.ts | 19 ++ .../endpoint/ingest_integration.test.ts | 5 +- .../server/endpoint/ingest_integration.ts | 6 + .../server/endpoint/mocks.ts | 3 +- .../artifacts/download_exception_list.test.ts | 14 +- .../endpoint/routes/metadata/handlers.ts | 20 +- .../server/endpoint/routes/metadata/index.ts | 7 +- .../endpoint/routes/metadata/metadata.test.ts | 6 +- .../routes/metadata/metadata_v1.test.ts | 6 +- .../routes/trusted_apps/handlers.test.ts | 16 +- .../endpoint/routes/trusted_apps/handlers.ts | 35 +- .../endpoint/routes/trusted_apps/index.ts | 4 +- .../plugins/security_solution/server/index.ts | 1 + .../routes/__mocks__/request_context.ts | 4 +- .../routes/__mocks__/server.ts | 7 +- .../routes/index/create_index_route.ts | 11 +- .../routes/index/delete_index_route.ts | 4 +- .../routes/index/read_index_route.ts | 4 +- .../privileges/read_privileges_route.test.ts | 1 + .../privileges/read_privileges_route.ts | 7 +- .../rules/add_prepackaged_rules_route.test.ts | 1 + .../rules/add_prepackaged_rules_route.ts | 11 +- .../rules/create_rules_bulk_route.test.ts | 1 + .../routes/rules/create_rules_bulk_route.ts | 7 +- .../routes/rules/create_rules_route.test.ts | 1 + .../routes/rules/create_rules_route.ts | 7 +- .../routes/rules/delete_rules_bulk_route.ts | 16 +- .../routes/rules/delete_rules_route.ts | 4 +- .../routes/rules/export_rules_route.ts | 4 +- .../routes/rules/find_rules_route.ts | 4 +- .../routes/rules/find_rules_status_route.ts | 4 +- .../get_prepackaged_rules_status_route.ts | 4 +- .../routes/rules/import_rules_route.test.ts | 1 + .../routes/rules/import_rules_route.ts | 8 +- .../routes/rules/patch_rules_bulk_route.ts | 7 +- .../routes/rules/patch_rules_route.ts | 4 +- .../routes/rules/read_rules_route.ts | 4 +- .../rules/update_rules_bulk_route.test.ts | 1 + .../routes/rules/update_rules_bulk_route.ts | 7 +- .../routes/rules/update_rules_route.test.ts | 1 + .../routes/rules/update_rules_route.ts | 4 +- .../signals/create_signals_migration_route.ts | 4 +- .../signals/delete_signals_migration_route.ts | 4 +- .../finalize_signals_migration_route.ts | 4 +- .../get_signals_migration_status_route.ts | 4 +- .../routes/signals/open_close_signals.test.ts | 1 + .../signals/open_close_signals_route.ts | 4 +- .../routes/signals/query_signals_route.ts | 4 +- .../routes/tags/read_tags_route.ts | 4 +- .../lib/framework/kibana_framework_adapter.ts | 12 +- .../server/lib/framework/types.ts | 5 +- .../routes/clean_draft_timelines_route.ts | 4 +- .../timeline/routes/create_timelines_route.ts | 4 +- .../timeline/routes/export_timelines_route.ts | 4 +- .../routes/get_draft_timelines_route.ts | 4 +- .../lib/timeline/routes/get_timeline_route.ts | 4 +- .../timeline/routes/import_timelines_route.ts | 4 +- .../install_prepacked_timelines_route.ts | 4 +- .../timeline/routes/update_timelines_route.ts | 4 +- .../lib/timeline/routes/utils/common.ts | 7 +- .../security_solution/server/lib/types.ts | 4 +- .../security_solution/server/plugin.ts | 8 +- .../security_solution/server/routes/index.ts | 4 +- .../plugins/security_solution/server/types.ts | 17 +- .../plugins/snapshot_restore/server/plugin.ts | 32 +- .../server/services/license.ts | 14 +- .../plugins/snapshot_restore/server/types.ts | 28 +- x-pack/plugins/spaces/server/plugin.ts | 5 +- .../server/routes/api/external/index.ts | 5 +- .../server/routes/api/internal/index.ts | 4 +- .../routes/lib/licensed_route_handler.ts | 18 +- x-pack/plugins/spaces/server/types.ts | 20 ++ .../lib/adapters/framework/adapter_types.ts | 6 +- .../server/lib/alerts/status_check.test.ts | 4 +- .../plugins/uptime/server/rest_api/types.ts | 8 +- x-pack/plugins/uptime/server/types.ts | 21 ++ x-pack/plugins/watcher/server/index.ts | 2 - .../license_pre_routing_factory.ts | 15 +- x-pack/plugins/watcher/server/plugin.ts | 36 +- x-pack/plugins/watcher/server/types.ts | 23 +- 382 files changed, 2306 insertions(+), 1873 deletions(-) create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md rename src/core/{utils => server/context/container}/context.mock.ts (92%) create mode 100644 src/core/server/context/container/context.test.ts rename src/core/{utils => server/context/container}/context.ts (85%) create mode 100644 src/core/server/context/container/index.ts delete mode 100644 src/core/utils/context.test.ts create mode 100644 src/plugins/vis_type_timeseries/server/types.ts create mode 100644 x-pack/plugins/data_enhanced/server/type.ts create mode 100644 x-pack/plugins/features/server/types.ts create mode 100644 x-pack/plugins/observability/server/types.ts create mode 100644 x-pack/plugins/security/server/types.ts create mode 100644 x-pack/plugins/spaces/server/types.ts create mode 100644 x-pack/plugins/uptime/server/types.ts diff --git a/docs/developer/architecture/core/index.asciidoc b/docs/developer/architecture/core/index.asciidoc index 48595690f9784..4a86c90cf8c10 100644 --- a/docs/developer/architecture/core/index.asciidoc +++ b/docs/developer/architecture/core/index.asciidoc @@ -421,29 +421,25 @@ the request handler context: [source,typescript] ---- -import type { CoreSetup, IScopedClusterClient } from 'kibana/server'; +import type { CoreSetup, RequestHandlerContext, IScopedClusterClient } from 'kibana/server'; -export interface MyPluginContext { - client: IScopedClusterClient; -} - -// extend RequestHandlerContext when a dependent plugin imports MyPluginContext from the file -declare module 'kibana/server' { - interface RequestHandlerContext { - myPlugin?: MyPluginContext; - } +interface MyRequestHandlerContext extends RequestHandlerContext { + myPlugin: { + client: IScopedClusterClient; + }; } class MyPlugin { setup(core: CoreSetup) { const client = core.elasticsearch.createClient('myClient'); - core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => { + core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => { return { client: client.asScoped(req) }; }); - const router = core.http.createRouter(); + const router = core.http.createRouter(); router.get( { path: '/api/my-plugin/', validate: … }, async (context, req, res) => { + // context type is inferred as MyPluginContext const data = await context.myPlugin.client.asCurrentUser('endpoint'); } ); diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresources.md b/docs/development/core/server/kibana-plugin-core-server.httpresources.md index cb3170e989e17..25acffc1a040f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.httpresources.md +++ b/docs/development/core/server/kibana-plugin-core-server.httpresources.md @@ -16,5 +16,5 @@ export interface HttpResources | Property | Type | Description | | --- | --- | --- | -| [register](./kibana-plugin-core-server.httpresources.register.md) | <P, Q, B>(route: RouteConfig<P, Q, B, 'get'>, handler: HttpResourcesRequestHandler<P, Q, B>) => void | To register a route handler executing passed function to form response. | +| [register](./kibana-plugin-core-server.httpresources.register.md) | <P, Q, B, Context extends RequestHandlerContext = RequestHandlerContext>(route: RouteConfig<P, Q, B, 'get'>, handler: HttpResourcesRequestHandler<P, Q, B, Context>) => void | To register a route handler executing passed function to form response. | diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresources.register.md b/docs/development/core/server/kibana-plugin-core-server.httpresources.register.md index fe3803a6ffe52..ee9569aeb37b4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.httpresources.register.md +++ b/docs/development/core/server/kibana-plugin-core-server.httpresources.register.md @@ -9,5 +9,5 @@ To register a route handler executing passed function to form response. Signature: ```typescript -register: (route: RouteConfig, handler: HttpResourcesRequestHandler) => void; +register: (route: RouteConfig, handler: HttpResourcesRequestHandler) => void; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.httpresourcesrequesthandler.md b/docs/development/core/server/kibana-plugin-core-server.httpresourcesrequesthandler.md index 20f930382955e..49854ac003860 100644 --- a/docs/development/core/server/kibana-plugin-core-server.httpresourcesrequesthandler.md +++ b/docs/development/core/server/kibana-plugin-core-server.httpresourcesrequesthandler.md @@ -9,7 +9,7 @@ Extended version of [RequestHandler](./kibana-plugin-core-server.requesthandler. Signature: ```typescript -export declare type HttpResourcesRequestHandler

= RequestHandler; +export declare type HttpResourcesRequestHandler

= RequestHandler; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.createrouter.md b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.createrouter.md index 89b9325145652..f009dd3fc2b16 100644 --- a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.createrouter.md +++ b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.createrouter.md @@ -9,7 +9,7 @@ Provides ability to declare a handler function for a particular path and HTTP re Signature: ```typescript -createRouter: () => IRouter; +createRouter: () => IRouter; ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md index 474dc6b7d6f28..dbc2a516fa17b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md @@ -84,7 +84,7 @@ async (context, request, response) => { | [auth](./kibana-plugin-core-server.httpservicesetup.auth.md) | HttpAuth | Auth status. See [HttpAuth](./kibana-plugin-core-server.httpauth.md) | | [basePath](./kibana-plugin-core-server.httpservicesetup.basepath.md) | IBasePath | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-core-server.ibasepath.md). | | [createCookieSessionStorageFactory](./kibana-plugin-core-server.httpservicesetup.createcookiesessionstoragefactory.md) | <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-core-server.sessionstoragefactory.md) | -| [createRouter](./kibana-plugin-core-server.httpservicesetup.createrouter.md) | () => IRouter | Provides ability to declare a handler function for a particular path and HTTP request method. | +| [createRouter](./kibana-plugin-core-server.httpservicesetup.createrouter.md) | <Context extends RequestHandlerContext = RequestHandlerContext>() => IRouter<Context> | Provides ability to declare a handler function for a particular path and HTTP request method. | | [csp](./kibana-plugin-core-server.httpservicesetup.csp.md) | ICspConfig | The CSP config used for Kibana. | | [getServerInfo](./kibana-plugin-core-server.httpservicesetup.getserverinfo.md) | () => HttpServerInfo | Provides common [information](./kibana-plugin-core-server.httpserverinfo.md) about the running http server. | | [registerAuth](./kibana-plugin-core-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | @@ -92,5 +92,5 @@ async (context, request, response) => { | [registerOnPreAuth](./kibana-plugin-core-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests before the Auth interceptor performs a check that user has access to requested resources. | | [registerOnPreResponse](./kibana-plugin-core-server.httpservicesetup.registeronpreresponse.md) | (handler: OnPreResponseHandler) => void | To define custom logic to perform for the server response. | | [registerOnPreRouting](./kibana-plugin-core-server.httpservicesetup.registeronprerouting.md) | (handler: OnPreRoutingHandler) => void | To define custom logic to perform for incoming requests before server performs a route lookup. | -| [registerRouteHandlerContext](./kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md) | <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer | Register a context provider for a route handler. | +| [registerRouteHandlerContext](./kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md) | <Context extends RequestHandlerContext, ContextName extends keyof Context>(contextName: ContextName, provider: RequestHandlerContextProvider<Context, ContextName>) => RequestHandlerContextContainer | Register a context provider for a route handler. | diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md index b0dc4d44f7559..df3f80580f6da 100644 --- a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md @@ -9,7 +9,7 @@ Register a context provider for a route handler. Signature: ```typescript -registerRouteHandlerContext: (contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; +registerRouteHandlerContext: (contextName: ContextName, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; ``` ## Example @@ -17,7 +17,10 @@ registerRouteHandlerContext: (contextName ```ts // my-plugin.ts - deps.http.registerRouteHandlerContext( + interface MyRequestHandlerContext extends RequestHandlerContext { + myApp: { search(id: string): Promise }; + } + deps.http.registerRouteHandlerContext( 'myApp', (context, req) => { async function search (id: string) { @@ -28,6 +31,8 @@ registerRouteHandlerContext: (contextName ); // my-route-handler.ts + import type { MyRequestHandlerContext } from './my-plugin.ts'; + const router = createRouter(); router.get({ path: '/', validate: false }, async (context, req, res) => { const response = await context.myApp.search(...); return res.ok(response); diff --git a/docs/development/core/server/kibana-plugin-core-server.icontextcontainer.md b/docs/development/core/server/kibana-plugin-core-server.icontextcontainer.md index 0c2cd7a69901f..3b390e3aaa117 100644 --- a/docs/development/core/server/kibana-plugin-core-server.icontextcontainer.md +++ b/docs/development/core/server/kibana-plugin-core-server.icontextcontainer.md @@ -9,7 +9,7 @@ An object that handles registration of context providers and configuring handler Signature: ```typescript -export interface IContextContainer> +export interface IContextContainer ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-core-server.icontextcontainer.registercontext.md b/docs/development/core/server/kibana-plugin-core-server.icontextcontainer.registercontext.md index 0813d81e5a72b..7f531fa8ba0d2 100644 --- a/docs/development/core/server/kibana-plugin-core-server.icontextcontainer.registercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.icontextcontainer.registercontext.md @@ -9,7 +9,7 @@ Register a new context provider. Signature: ```typescript -registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; +registerContext(pluginOpaqueId: PluginOpaqueId, contextName: ContextName, provider: IContextProvider): this; ``` ## Parameters @@ -17,8 +17,8 @@ registerContext>(pluginO | Parameter | Type | Description | | --- | --- | --- | | pluginOpaqueId | PluginOpaqueId | The plugin opaque ID for the plugin that registers this context. | -| contextName | TContextName | The key of the TContext object this provider supplies the value for. | -| provider | IContextProvider<THandler, TContextName> | A [IContextProvider](./kibana-plugin-core-server.icontextprovider.md) to be called each time a new context is created. | +| contextName | ContextName | The key of the TContext object this provider supplies the value for. | +| provider | IContextProvider<Context, ContextName> | A [IContextProvider](./kibana-plugin-core-server.icontextprovider.md) to be called each time a new context is created. | Returns: diff --git a/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md b/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md index 7d124b266bcc1..ddd8a0e92f465 100644 --- a/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md +++ b/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md @@ -9,7 +9,7 @@ A function that returns a context value for a specific key of given context type Signature: ```typescript -export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: PartialExceptFor, 'core'>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +export declare type IContextProvider = (context: Omit, ...rest: HandlerParameters) => Promise | Context[ContextName]; ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-core-server.irouter.delete.md b/docs/development/core/server/kibana-plugin-core-server.irouter.delete.md index d4c4692239d79..a7b6dd5bc294e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.irouter.delete.md +++ b/docs/development/core/server/kibana-plugin-core-server.irouter.delete.md @@ -9,5 +9,5 @@ Register a route handler for `DELETE` request. Signature: ```typescript -delete: RouteRegistrar<'delete'>; +delete: RouteRegistrar<'delete', Context>; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.irouter.get.md b/docs/development/core/server/kibana-plugin-core-server.irouter.get.md index 38ed9ea96455e..7db694b38da47 100644 --- a/docs/development/core/server/kibana-plugin-core-server.irouter.get.md +++ b/docs/development/core/server/kibana-plugin-core-server.irouter.get.md @@ -9,5 +9,5 @@ Register a route handler for `GET` request. Signature: ```typescript -get: RouteRegistrar<'get'>; +get: RouteRegistrar<'get', Context>; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.irouter.md b/docs/development/core/server/kibana-plugin-core-server.irouter.md index 4bade638a65a5..a0a27e828f865 100644 --- a/docs/development/core/server/kibana-plugin-core-server.irouter.md +++ b/docs/development/core/server/kibana-plugin-core-server.irouter.md @@ -9,18 +9,18 @@ Registers route handlers for specified resource path and method. See [RouteConfi Signature: ```typescript -export interface IRouter +export interface IRouter ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [delete](./kibana-plugin-core-server.irouter.delete.md) | RouteRegistrar<'delete'> | Register a route handler for DELETE request. | -| [get](./kibana-plugin-core-server.irouter.get.md) | RouteRegistrar<'get'> | Register a route handler for GET request. | +| [delete](./kibana-plugin-core-server.irouter.delete.md) | RouteRegistrar<'delete', Context> | Register a route handler for DELETE request. | +| [get](./kibana-plugin-core-server.irouter.get.md) | RouteRegistrar<'get', Context> | Register a route handler for GET request. | | [handleLegacyErrors](./kibana-plugin-core-server.irouter.handlelegacyerrors.md) | RequestHandlerWrapper | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. | -| [patch](./kibana-plugin-core-server.irouter.patch.md) | RouteRegistrar<'patch'> | Register a route handler for PATCH request. | -| [post](./kibana-plugin-core-server.irouter.post.md) | RouteRegistrar<'post'> | Register a route handler for POST request. | -| [put](./kibana-plugin-core-server.irouter.put.md) | RouteRegistrar<'put'> | Register a route handler for PUT request. | +| [patch](./kibana-plugin-core-server.irouter.patch.md) | RouteRegistrar<'patch', Context> | Register a route handler for PATCH request. | +| [post](./kibana-plugin-core-server.irouter.post.md) | RouteRegistrar<'post', Context> | Register a route handler for POST request. | +| [put](./kibana-plugin-core-server.irouter.put.md) | RouteRegistrar<'put', Context> | Register a route handler for PUT request. | | [routerPath](./kibana-plugin-core-server.irouter.routerpath.md) | string | Resulted path | diff --git a/docs/development/core/server/kibana-plugin-core-server.irouter.patch.md b/docs/development/core/server/kibana-plugin-core-server.irouter.patch.md index f835eb9800735..b353079630ecb 100644 --- a/docs/development/core/server/kibana-plugin-core-server.irouter.patch.md +++ b/docs/development/core/server/kibana-plugin-core-server.irouter.patch.md @@ -9,5 +9,5 @@ Register a route handler for `PATCH` request. Signature: ```typescript -patch: RouteRegistrar<'patch'>; +patch: RouteRegistrar<'patch', Context>; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.irouter.post.md b/docs/development/core/server/kibana-plugin-core-server.irouter.post.md index 312b83d570a42..94c703ad6f339 100644 --- a/docs/development/core/server/kibana-plugin-core-server.irouter.post.md +++ b/docs/development/core/server/kibana-plugin-core-server.irouter.post.md @@ -9,5 +9,5 @@ Register a route handler for `POST` request. Signature: ```typescript -post: RouteRegistrar<'post'>; +post: RouteRegistrar<'post', Context>; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.irouter.put.md b/docs/development/core/server/kibana-plugin-core-server.irouter.put.md index d8a8271b6fc9e..702ff3ff61bb6 100644 --- a/docs/development/core/server/kibana-plugin-core-server.irouter.put.md +++ b/docs/development/core/server/kibana-plugin-core-server.irouter.put.md @@ -9,5 +9,5 @@ Register a route handler for `PUT` request. Signature: ```typescript -put: RouteRegistrar<'put'>; +put: RouteRegistrar<'put', Context>; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandler.md b/docs/development/core/server/kibana-plugin-core-server.requesthandler.md index cecef7c923568..0032e52a0e906 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandler.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandler.md @@ -9,7 +9,7 @@ A function executed when route path matched requested resource path. Request han Signature: ```typescript -export declare type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, response: ResponseFactory) => IKibanaResponse | Promise>; +export declare type RequestHandler

= (context: Context, request: KibanaRequest, response: ResponseFactory) => IKibanaResponse | Promise>; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontextcontainer.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontextcontainer.md index c95a16670b190..6966deb9d7cc7 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontextcontainer.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontextcontainer.md @@ -9,5 +9,5 @@ An object that handles registration of http request context providers. Signature: ```typescript -export declare type RequestHandlerContextContainer = IContextContainer>; +export declare type RequestHandlerContextContainer = IContextContainer; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontextprovider.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontextprovider.md index cd30b3c1f43e3..d94facd849eff 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontextprovider.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontextprovider.md @@ -9,5 +9,5 @@ Context provider for request handler. Extends request context object with provid Signature: ```typescript -export declare type RequestHandlerContextProvider = IContextProvider, TContextName>; +export declare type RequestHandlerContextProvider = IContextProvider; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlerwrapper.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlerwrapper.md index a9fe188ee2bff..76c7ee4f22902 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlerwrapper.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlerwrapper.md @@ -9,7 +9,7 @@ Type-safe wrapper for [RequestHandler](./kibana-plugin-core-server.requesthandle Signature: ```typescript -export declare type RequestHandlerWrapper = (handler: RequestHandler) => RequestHandler; +export declare type RequestHandlerWrapper = (handler: RequestHandler) => RequestHandler; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-core-server.routeregistrar.md b/docs/development/core/server/kibana-plugin-core-server.routeregistrar.md index 121a8eee1bfcd..3ddb350a38b6f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.routeregistrar.md +++ b/docs/development/core/server/kibana-plugin-core-server.routeregistrar.md @@ -9,5 +9,5 @@ Route handler common definition Signature: ```typescript -export declare type RouteRegistrar = (route: RouteConfig, handler: RequestHandler) => void; +export declare type RouteRegistrar = (route: RouteConfig, handler: RequestHandler) => void; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md index 3bc2a20541777..496e1ae9677d8 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md @@ -15,13 +15,13 @@ Using `createSearchSource`, the instance can be re-created. ```typescript serialize(): { searchSourceJSON: string; - references: import("src/core/server").SavedObjectReference[]; + references: import("../../../../../core/types").SavedObjectReference[]; }; ``` Returns: `{ searchSourceJSON: string; - references: import("src/core/server").SavedObjectReference[]; + references: import("../../../../../core/types").SavedObjectReference[]; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md new file mode 100644 index 0000000000000..8b7b025d80181 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md) + +## DataApiRequestHandlerContext interface + +Signature: + +```typescript +export interface DataApiRequestHandlerContext extends ISearchClient +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [session](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md) | IScopedSessionService | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md new file mode 100644 index 0000000000000..9a6e3f55d3929 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md) > [session](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md) + +## DataApiRequestHandlerContext.session property + +Signature: + +```typescript +session: IScopedSessionService; +``` 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 e6cb5accb9e31..84c7875c26ce8 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 @@ -45,6 +45,7 @@ | --- | --- | | [AggFunctionsMapping](./kibana-plugin-plugins-data-server.aggfunctionsmapping.md) | A global list of the expression function definitions for each agg type function. | | [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) | | +| [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.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) | | 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 8f1ea7b95a5f9..af7abb076d7ef 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 @@ -9,10 +9,10 @@ ```typescript start(core: CoreStart): { fieldFormats: { - fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; + fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -28,10 +28,10 @@ start(core: CoreStart): { `{ fieldFormats: { - fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; + fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/examples/search_examples/server/plugin.ts b/examples/search_examples/server/plugin.ts index 605d4b0ec8291..e7ee311c8d652 100644 --- a/examples/search_examples/server/plugin.ts +++ b/examples/search_examples/server/plugin.ts @@ -6,13 +6,16 @@ * Public License, v 1. */ -import { +import type { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger, -} from '../../../src/core/server'; + RequestHandlerContext, +} from 'src/core/server'; + +import type { DataApiRequestHandlerContext } from 'src/plugins/data/server'; import { SearchExamplesPluginSetup, @@ -42,12 +45,14 @@ export class SearchExamplesPlugin deps: SearchExamplesPluginSetupDeps ) { this.logger.debug('search_examples: Setup'); - const router = core.http.createRouter(); + const router = core.http.createRouter< + RequestHandlerContext & { search: DataApiRequestHandlerContext } + >(); core.getStartServices().then(([_, depsStart]) => { const myStrategy = mySearchStrategyProvider(depsStart.data); deps.data.search.registerSearchStrategy('myStrategy', myStrategy); - registerRoutes(router, depsStart.data); + registerRoutes(router); }); return {}; diff --git a/examples/search_examples/server/routes/register_routes.ts b/examples/search_examples/server/routes/register_routes.ts index 87d2e96137736..d7a18509b9a79 100644 --- a/examples/search_examples/server/routes/register_routes.ts +++ b/examples/search_examples/server/routes/register_routes.ts @@ -6,10 +6,12 @@ * Public License, v 1. */ -import { IRouter } from 'kibana/server'; -import { PluginStart as DataPluginStart } from 'src/plugins/data/server'; +import type { IRouter, RequestHandlerContext } from 'kibana/server'; +import { DataApiRequestHandlerContext } from 'src/plugins/data/server'; import { registerServerSearchRoute } from './server_search_route'; -export function registerRoutes(router: IRouter, data: DataPluginStart) { - registerServerSearchRoute(router, data); +export function registerRoutes( + router: IRouter +) { + registerServerSearchRoute(router); } diff --git a/examples/search_examples/server/routes/server_search_route.ts b/examples/search_examples/server/routes/server_search_route.ts index c16ba55b8abec..99a1aba99d8ec 100644 --- a/examples/search_examples/server/routes/server_search_route.ts +++ b/examples/search_examples/server/routes/server_search_route.ts @@ -6,13 +6,16 @@ * Public License, v 1. */ -import { PluginStart as DataPluginStart, IEsSearchRequest } from 'src/plugins/data/server'; +import { IEsSearchRequest } from 'src/plugins/data/server'; import { schema } from '@kbn/config-schema'; import { IEsSearchResponse } from 'src/plugins/data/common'; -import { IRouter } from '../../../../src/core/server'; +import type { DataApiRequestHandlerContext } from 'src/plugins/data/server'; +import type { IRouter, RequestHandlerContext } from 'src/core/server'; import { SERVER_SEARCH_ROUTE_PATH } from '../../common'; -export function registerServerSearchRoute(router: IRouter, data: DataPluginStart) { +export function registerServerSearchRoute( + router: IRouter +) { router.get( { path: SERVER_SEARCH_ROUTE_PATH, diff --git a/src/core/utils/context.mock.ts b/src/core/server/context/container/context.mock.ts similarity index 92% rename from src/core/utils/context.mock.ts rename to src/core/server/context/container/context.mock.ts index fe15cbc7681d5..7f5a8ef28aab1 100644 --- a/src/core/utils/context.mock.ts +++ b/src/core/server/context/container/context.mock.ts @@ -12,6 +12,7 @@ export type ContextContainerMock = jest.Mocked>; const createContextMock = (mockContext = {}) => { const contextMock: ContextContainerMock = { + // @ts-expect-error tsc cannot infer ContextName and uses never registerContext: jest.fn(), createHandler: jest.fn(), }; diff --git a/src/core/server/context/container/context.test.ts b/src/core/server/context/container/context.test.ts new file mode 100644 index 0000000000000..0e697d64ed2ec --- /dev/null +++ b/src/core/server/context/container/context.test.ts @@ -0,0 +1,319 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { ContextContainer } from './context'; +import { PluginOpaqueId } from '../..'; +import { httpServerMock } from '../../http/http_server.mocks'; + +const pluginA = Symbol('pluginA'); +const pluginB = Symbol('pluginB'); +const pluginC = Symbol('pluginC'); +const pluginD = Symbol('pluginD'); +const plugins: ReadonlyMap = new Map([ + [pluginA, []], + [pluginB, [pluginA]], + [pluginC, [pluginA, pluginB]], + [pluginD, []], +]); +const coreId = Symbol(); + +interface MyContext { + core: any; + core1: string; + core2: number; + ctxFromA: string; + ctxFromB: number; + ctxFromC: boolean; + ctxFromD: object; +} + +describe('ContextContainer', () => { + it('does not allow the same context to be registered twice', () => { + const contextContainer = new ContextContainer(plugins, coreId); + contextContainer.registerContext<{ ctxFromA: string; core: any }, 'ctxFromA'>( + coreId, + 'ctxFromA', + () => 'aString' + ); + + expect(() => + contextContainer.registerContext<{ ctxFromA: string; core: any }, 'ctxFromA'>( + coreId, + 'ctxFromA', + () => 'aString' + ) + ).toThrowErrorMatchingInlineSnapshot( + `"Context provider for ctxFromA has already been registered."` + ); + }); + + describe('registerContext', () => { + it('throws error if called with an unknown symbol', async () => { + const contextContainer = new ContextContainer(plugins, coreId); + await expect(() => + contextContainer.registerContext<{ ctxFromA: string; core: any }, 'ctxFromA'>( + Symbol('unknown'), + 'ctxFromA', + jest.fn() + ) + ).toThrowErrorMatchingInlineSnapshot( + `"Cannot register context for unknown plugin: Symbol(unknown)"` + ); + }); + }); + + describe('context building', () => { + it('resolves dependencies', async () => { + const contextContainer = new ContextContainer(plugins, coreId); + expect.assertions(8); + contextContainer.registerContext<{ core1: string; core: any }, 'core1'>( + coreId, + 'core1', + (context) => { + expect(context).toEqual({}); + return 'core'; + } + ); + + contextContainer.registerContext<{ ctxFromA: string; core: any }, 'ctxFromA'>( + pluginA, + 'ctxFromA', + (context) => { + expect(context).toEqual({ core1: 'core' }); + return 'aString'; + } + ); + contextContainer.registerContext<{ ctxFromB: number; core: any }, 'ctxFromB'>( + pluginB, + 'ctxFromB', + (context) => { + expect(context).toEqual({ core1: 'core', ctxFromA: 'aString' }); + return 299; + } + ); + contextContainer.registerContext<{ ctxFromC: boolean; core: any }, 'ctxFromC'>( + pluginC, + 'ctxFromC', + (context) => { + expect(context).toEqual({ + core1: 'core', + ctxFromA: 'aString', + ctxFromB: 299, + }); + return false; + } + ); + contextContainer.registerContext<{ ctxFromD: {}; core: any }, 'ctxFromD'>( + pluginD, + 'ctxFromD', + (context) => { + expect(context).toEqual({ core1: 'core' }); + return {}; + } + ); + + const rawHandler1 = jest.fn(() => 'handler1' as any); + const handler1 = contextContainer.createHandler(pluginC, rawHandler1); + + const rawHandler2 = jest.fn(() => 'handler2' as any); + const handler2 = contextContainer.createHandler(pluginD, rawHandler2); + + const request = httpServerMock.createKibanaRequest(); + const response = httpServerMock.createResponseFactory(); + + await handler1(request, response); + await handler2(request, response); + + // Should have context from pluginC, its deps, and core + expect(rawHandler1).toHaveBeenCalledWith( + { + core1: 'core', + ctxFromA: 'aString', + ctxFromB: 299, + ctxFromC: false, + }, + request, + response + ); + + // Should have context from pluginD, and core + expect(rawHandler2).toHaveBeenCalledWith( + { + core1: 'core', + ctxFromD: {}, + }, + request, + response + ); + }); + + it('exposes all core context to all providers regardless of registration order', async () => { + expect.assertions(4); + + const contextContainer = new ContextContainer(plugins, coreId); + contextContainer + .registerContext(pluginA, 'ctxFromA', (context) => { + expect(context).toEqual({ core1: 'core', core2: 101 }); + return `aString ${context.core1} ${context.core2}`; + }) + .registerContext(coreId, 'core1', () => 'core') + .registerContext(coreId, 'core2', () => 101) + .registerContext(pluginB, 'ctxFromB', (context) => { + expect(context).toEqual({ + core1: 'core', + core2: 101, + ctxFromA: 'aString core 101', + }); + return 277; + }); + + const rawHandler1 = jest.fn(() => 'handler1' as any); + const handler1 = contextContainer.createHandler(pluginB, rawHandler1); + + const request = httpServerMock.createKibanaRequest(); + const response = httpServerMock.createResponseFactory(); + expect(await handler1(request, response)).toEqual('handler1'); + + expect(rawHandler1).toHaveBeenCalledWith( + { + core1: 'core', + core2: 101, + ctxFromA: 'aString core 101', + ctxFromB: 277, + }, + request, + response + ); + }); + + it('exposes all core context to core providers', async () => { + expect.assertions(4); + const contextContainer = new ContextContainer(plugins, coreId); + + contextContainer + .registerContext(coreId, 'core1', (context) => { + expect(context).toEqual({}); + return 'core'; + }) + .registerContext(coreId, 'core2', (context) => { + expect(context).toEqual({ core1: 'core' }); + return 101; + }); + + const rawHandler1 = jest.fn(() => 'handler1' as any); + const handler1 = contextContainer.createHandler(pluginA, rawHandler1); + + const request = httpServerMock.createKibanaRequest(); + const response = httpServerMock.createResponseFactory(); + expect(await handler1(request, response)).toEqual('handler1'); + + // If no context is registered for pluginA, only core contexts should be exposed + expect(rawHandler1).toHaveBeenCalledWith( + { + core1: 'core', + core2: 101, + }, + request, + response + ); + }); + + it('does not expose plugin contexts to core handler', async () => { + const contextContainer = new ContextContainer(plugins, coreId); + + contextContainer + .registerContext(coreId, 'core1', (context) => 'core') + .registerContext(pluginA, 'ctxFromA', (context) => 'aString'); + + const rawHandler1 = jest.fn(() => 'handler1' as any); + const handler1 = contextContainer.createHandler(coreId, rawHandler1); + + const request = httpServerMock.createKibanaRequest(); + const response = httpServerMock.createResponseFactory(); + expect(await handler1(request, response)).toEqual('handler1'); + // pluginA context should not be present in a core handler + expect(rawHandler1).toHaveBeenCalledWith( + { + core1: 'core', + }, + request, + response + ); + }); + + it('passes additional arguments to providers', async () => { + expect.assertions(6); + const contextContainer = new ContextContainer(plugins, coreId); + + const request = httpServerMock.createKibanaRequest(); + const response = httpServerMock.createResponseFactory(); + contextContainer.registerContext(coreId, 'core1', (context, req, res) => { + expect(req).toBe(request); + expect(res).toBe(response); + return 'core'; + }); + + contextContainer.registerContext( + pluginD, + 'ctxFromB', + (context, req, res) => { + expect(req).toBe(request); + expect(res).toBe(response); + return 77; + } + ); + + const rawHandler1 = jest.fn(() => 'handler1' as any); + const handler1 = contextContainer.createHandler(pluginD, rawHandler1); + + expect(await handler1(request, response)).toEqual('handler1'); + + expect(rawHandler1).toHaveBeenCalledWith( + { + core1: 'core', + ctxFromB: 77, + }, + request, + response + ); + }); + }); + + describe('createHandler', () => { + it('throws error if called with an unknown symbol', async () => { + const contextContainer = new ContextContainer(plugins, coreId); + await expect(() => + contextContainer.createHandler(Symbol('unknown'), jest.fn()) + ).toThrowErrorMatchingInlineSnapshot( + `"Cannot create handler for unknown plugin: Symbol(unknown)"` + ); + }); + + it('returns value from original handler', async () => { + const contextContainer = new ContextContainer(plugins, coreId); + const rawHandler1 = jest.fn(() => 'handler1' as any); + const handler1 = contextContainer.createHandler(pluginA, rawHandler1); + + const request = httpServerMock.createKibanaRequest(); + const response = httpServerMock.createResponseFactory(); + expect(await handler1(request, response)).toEqual('handler1'); + }); + + it('passes additional arguments to handlers', async () => { + const contextContainer = new ContextContainer(plugins, coreId); + + const rawHandler1 = jest.fn(() => 'handler1' as any); + const handler1 = contextContainer.createHandler(pluginA, rawHandler1); + + const request = httpServerMock.createKibanaRequest(); + const response = httpServerMock.createResponseFactory(); + await handler1(request, response); + expect(rawHandler1).toHaveBeenCalledWith({}, request, response); + }); + }); +}); diff --git a/src/core/utils/context.ts b/src/core/server/context/container/context.ts similarity index 85% rename from src/core/utils/context.ts rename to src/core/server/context/container/context.ts index f3d4370013827..234fb7d34700d 100644 --- a/src/core/utils/context.ts +++ b/src/core/server/context/container/context.ts @@ -8,13 +8,8 @@ import { flatten } from 'lodash'; import { ShallowPromise } from '@kbn/utility-types'; -import { pick } from '@kbn/std'; -import type { CoreId, PluginOpaqueId } from '../server'; - -/** - * Make all properties in T optional, except for the properties whose keys are in the union K - */ -type PartialExceptFor = Partial & Pick; +import { pick } from 'lodash'; +import type { CoreId, PluginOpaqueId, RequestHandler, RequestHandlerContext } from '../..'; /** * A function that returns a context value for a specific key of given context type. @@ -30,15 +25,13 @@ type PartialExceptFor = Partial & Pick; * @public */ export type IContextProvider< - THandler extends HandlerFunction, - TContextName extends keyof HandlerContextType + Context extends RequestHandlerContext, + ContextName extends keyof Context > = ( // context.core will always be available, but plugin contexts are typed as optional - context: PartialExceptFor, 'core'>, - ...rest: HandlerParameters -) => - | Promise[TContextName]> - | HandlerContextType[TContextName]; + context: Omit, + ...rest: HandlerParameters +) => Promise | Context[ContextName]; /** * A function that accepts a context object and an optional number of additional arguments. Used for the generic types @@ -142,7 +135,7 @@ export type HandlerParameters> = T extends ( * * @public */ -export interface IContextContainer> { +export interface IContextContainer { /** * Register a new context provider. * @@ -157,10 +150,10 @@ export interface IContextContainer> { * @param provider - A {@link IContextProvider} to be called each time a new context is created. * @returns The {@link IContextContainer} for method chaining. */ - registerContext>( + registerContext( pluginOpaqueId: PluginOpaqueId, - contextName: TContextName, - provider: IContextProvider + contextName: ContextName, + provider: IContextProvider ): this; /** @@ -178,21 +171,21 @@ export interface IContextContainer> { } /** @internal */ -export class ContextContainer> +export class ContextContainer implements IContextContainer { /** * Used to map contexts to their providers and associated plugin. In registration order which is tightly coupled to * plugin load order. */ private readonly contextProviders = new Map< - keyof HandlerContextType, + string, { - provider: IContextProvider>; + provider: IContextProvider; source: symbol; } >(); /** Used to keep track of which plugins registered which contexts for dependency resolution. */ - private readonly contextNamesBySource: Map>>; + private readonly contextNamesBySource: Map; /** * @param pluginDependencies - A map of plugins to an array of their dependencies. @@ -201,16 +194,18 @@ export class ContextContainer> private readonly pluginDependencies: ReadonlyMap, private readonly coreId: CoreId ) { - this.contextNamesBySource = new Map>>([ - [coreId, []], - ]); + this.contextNamesBySource = new Map([[coreId, []]]); } - public registerContext = >( + public registerContext = < + Context extends RequestHandlerContext, + ContextName extends keyof Context + >( source: symbol, - contextName: TContextName, - provider: IContextProvider + name: ContextName, + provider: IContextProvider ): this => { + const contextName = name as string; if (this.contextProviders.has(contextName)) { throw new Error(`Context provider for ${contextName} has already been registered.`); } @@ -234,6 +229,7 @@ export class ContextContainer> return (async (...args: HandlerParameters) => { const context = await this.buildContext(source, ...args); + // @ts-expect-error requires explicit handler arity return handler(context, ...args); }) as (...args: HandlerParameters) => ShallowPromise>; }; @@ -242,9 +238,7 @@ export class ContextContainer> source: symbol, ...contextArgs: HandlerParameters ): Promise> { - const contextsToBuild: ReadonlySet> = new Set( - this.getContextNamesForSource(source) - ); + const contextsToBuild = new Set(this.getContextNamesForSource(source)); return [...this.contextProviders] .sort(sortByCoreFirst(this.coreId)) @@ -256,18 +250,17 @@ export class ContextContainer> // registered that provider. const exposedContext = pick(resolvedContext, [ ...this.getContextNamesForSource(providerSource), - ]) as PartialExceptFor, 'core'>; + ]); return { ...resolvedContext, + // @ts-expect-error requires explicit provider arity [contextName]: await provider(exposedContext, ...contextArgs), }; }, Promise.resolve({}) as Promise>); } - private getContextNamesForSource( - source: symbol - ): ReadonlySet> { + private getContextNamesForSource(source: symbol): ReadonlySet { if (source === this.coreId) { return this.getContextNamesForCore(); } else { diff --git a/src/core/server/context/container/index.ts b/src/core/server/context/container/index.ts new file mode 100644 index 0000000000000..b553c1b65223c --- /dev/null +++ b/src/core/server/context/container/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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +export * from './context'; diff --git a/src/core/server/context/context_service.mock.ts b/src/core/server/context/context_service.mock.ts index 1ce1b26e3a4dc..c849c0b6b6974 100644 --- a/src/core/server/context/context_service.mock.ts +++ b/src/core/server/context/context_service.mock.ts @@ -9,7 +9,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { ContextService, ContextSetup } from './context_service'; -import { contextMock } from '../../utils/context.mock'; +import { contextMock } from './container/context.mock'; const createSetupContractMock = (mockContext = {}) => { const setupContract: jest.Mocked = { diff --git a/src/core/server/context/context_service.test.mocks.ts b/src/core/server/context/context_service.test.mocks.ts index 2b6fedb15c8c5..7a69afb002c35 100644 --- a/src/core/server/context/context_service.test.mocks.ts +++ b/src/core/server/context/context_service.test.mocks.ts @@ -6,9 +6,9 @@ * Public License, v 1. */ -import { contextMock } from '../../utils/context.mock'; +import { contextMock } from './container/context.mock'; export const MockContextConstructor = jest.fn(contextMock.create); -jest.doMock('../../utils/context', () => ({ +jest.doMock('./container/context', () => ({ ContextContainer: MockContextConstructor, })); diff --git a/src/core/server/context/context_service.ts b/src/core/server/context/context_service.ts index 7a9ee4e3d35c2..1f4bd7c2e3af2 100644 --- a/src/core/server/context/context_service.ts +++ b/src/core/server/context/context_service.ts @@ -7,7 +7,7 @@ */ import { PluginOpaqueId } from '../../server'; -import { IContextContainer, ContextContainer, HandlerFunction } from '../../utils/context'; +import { IContextContainer, ContextContainer, HandlerFunction } from './container'; import { CoreContext } from '../core_context'; interface SetupDeps { diff --git a/src/core/server/context/index.ts b/src/core/server/context/index.ts index b9a8e0d7f498f..8c690034368d9 100644 --- a/src/core/server/context/index.ts +++ b/src/core/server/context/index.ts @@ -13,4 +13,4 @@ export { HandlerFunction, HandlerContextType, HandlerParameters, -} from '../../utils/context'; +} from './container'; diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index eb90783bb4647..f9d6ef3cb38d1 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -89,6 +89,7 @@ const createInternalSetupContractMock = () => { registerOnPreAuth: jest.fn(), registerAuth: jest.fn(), registerOnPostAuth: jest.fn(), + // @ts-expect-error tsc cannot infer ContextName and uses never registerRouteHandlerContext: jest.fn(), registerOnPreResponse: jest.fn(), createRouter: jest.fn().mockImplementation(() => mockRouter.create({})), @@ -125,6 +126,7 @@ const createSetupContractMock = () => { basePath: internalMock.basePath, csp: CspConfig.DEFAULT, createRouter: jest.fn(), + // @ts-expect-error tsc cannot infer ContextName and uses never registerRouteHandlerContext: jest.fn(), auth: { get: internalMock.auth.get, diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 0a3dfa12e1df5..0e4ab8ec4cd66 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -11,6 +11,7 @@ import { first, map } from 'rxjs/operators'; import { Server } from '@hapi/hapi'; import { pick } from '@kbn/std'; +import type { RequestHandlerContext } from 'src/core/server'; import { CoreService } from '../../types'; import { Logger, LoggerFactory } from '../logging'; import { ContextSetup } from '../context'; @@ -31,7 +32,6 @@ import { InternalHttpServiceStart, } from './types'; -import { RequestHandlerContext } from '../../server'; import { registerCoreHandlers } from './lifecycle_handlers'; import { ExternalUrlConfigType, @@ -100,17 +100,23 @@ export class HttpService externalUrl: new ExternalUrlConfig(config.externalUrl), - createRouter: (path: string, pluginId: PluginOpaqueId = this.coreContext.coreId) => { + createRouter: ( + path: string, + pluginId: PluginOpaqueId = this.coreContext.coreId + ) => { const enhanceHandler = this.requestHandlerContext!.createHandler.bind(null, pluginId); - const router = new Router(path, this.log, enhanceHandler); + const router = new Router(path, this.log, enhanceHandler); registerRouter(router); return router; }, - registerRouteHandlerContext: ( + registerRouteHandlerContext: < + Context extends RequestHandlerContext, + ContextName extends keyof Context + >( pluginOpaqueId: PluginOpaqueId, - contextName: T, - provider: RequestHandlerContextProvider + contextName: ContextName, + provider: RequestHandlerContextProvider ) => this.requestHandlerContext!.registerContext(pluginOpaqueId, contextName, provider), }; diff --git a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts index 3fdc452101576..349758d9c3912 100644 --- a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts +++ b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts @@ -175,19 +175,19 @@ describe('core lifecycle handlers', () => { }); destructiveMethods.forEach((method) => { - ((router as any)[method.toLowerCase()] as RouteRegistrar)( + ((router as any)[method.toLowerCase()] as RouteRegistrar)( { path: testPath, validate: false }, (context, req, res) => { return res.ok({ body: 'ok' }); } ); - ((router as any)[method.toLowerCase()] as RouteRegistrar)( + ((router as any)[method.toLowerCase()] as RouteRegistrar)( { path: allowlistedTestPath, validate: false }, (context, req, res) => { return res.ok({ body: 'ok' }); } ); - ((router as any)[method.toLowerCase()] as RouteRegistrar)( + ((router as any)[method.toLowerCase()] as RouteRegistrar)( { path: xsrfDisabledTestPath, validate: false, options: { xsrfRequired: false } }, (context, req, res) => { return res.ok({ body: 'ok' }); diff --git a/src/core/server/http/router/router.mock.ts b/src/core/server/http/router/router.mock.ts index ee515a3ffddf8..b3ff8ce983abf 100644 --- a/src/core/server/http/router/router.mock.ts +++ b/src/core/server/http/router/router.mock.ts @@ -8,7 +8,7 @@ import { IRouter } from './router'; -export type RouterMock = jest.Mocked; +export type RouterMock = jest.Mocked>; function create({ routerPath = '' }: { routerPath?: string } = {}): RouterMock { return { diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index 4a5db793b0b0f..1368714aa993f 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -44,9 +44,12 @@ interface RouterRoute { * * @public */ -export type RouteRegistrar = ( +export type RouteRegistrar< + Method extends RouteMethod, + Context extends RequestHandlerContext = RequestHandlerContext +> = ( route: RouteConfig, - handler: RequestHandler + handler: RequestHandler ) => void; /** @@ -55,7 +58,7 @@ export type RouteRegistrar = ( * * @public */ -export interface IRouter { +export interface IRouter { /** * Resulted path */ @@ -66,35 +69,35 @@ export interface IRouter { * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - get: RouteRegistrar<'get'>; + get: RouteRegistrar<'get', Context>; /** * Register a route handler for `POST` request. * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - post: RouteRegistrar<'post'>; + post: RouteRegistrar<'post', Context>; /** * Register a route handler for `PUT` request. * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - put: RouteRegistrar<'put'>; + put: RouteRegistrar<'put', Context>; /** * Register a route handler for `PATCH` request. * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - patch: RouteRegistrar<'patch'>; + patch: RouteRegistrar<'patch', Context>; /** * Register a route handler for `DELETE` request. * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - delete: RouteRegistrar<'delete'>; + delete: RouteRegistrar<'delete', Context>; /** * Wrap a router handler to catch and converts legacy boom errors to proper custom errors. @@ -110,9 +113,13 @@ export interface IRouter { getRoutes: () => RouterRoute[]; } -export type ContextEnhancer = ( - handler: RequestHandler -) => RequestHandlerEnhanced; +export type ContextEnhancer< + P, + Q, + B, + Method extends RouteMethod, + Context extends RequestHandlerContext +> = (handler: RequestHandler) => RequestHandlerEnhanced; function getRouteFullPath(routerPath: string, routePath: string) { // If router's path ends with slash and route's path starts with slash, @@ -195,22 +202,23 @@ function validOptions( /** * @internal */ -export class Router implements IRouter { +export class Router + implements IRouter { public routes: Array> = []; - public get: IRouter['get']; - public post: IRouter['post']; - public delete: IRouter['delete']; - public put: IRouter['put']; - public patch: IRouter['patch']; + public get: IRouter['get']; + public post: IRouter['post']; + public delete: IRouter['delete']; + public put: IRouter['put']; + public patch: IRouter['patch']; constructor( public readonly routerPath: string, private readonly log: Logger, - private readonly enhanceWithContext: ContextEnhancer + private readonly enhanceWithContext: ContextEnhancer ) { const buildMethod = (method: Method) => ( route: RouteConfig, - handler: RequestHandler + handler: RequestHandler ) => { const routeSchemas = routeSchemasFromRouteConfig(route, method); @@ -300,7 +308,7 @@ type WithoutHeadArgument = T extends (first: any, ...rest: infer Params) => i : never; type RequestHandlerEnhanced = WithoutHeadArgument< - RequestHandler + RequestHandler >; /** @@ -341,10 +349,11 @@ export type RequestHandler< P = unknown, Q = unknown, B = unknown, + Context extends RequestHandlerContext = RequestHandlerContext, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory > = ( - context: RequestHandlerContext, + context: Context, request: KibanaRequest, response: ResponseFactory ) => IKibanaResponse | Promise>; @@ -366,8 +375,9 @@ export type RequestHandlerWrapper = < P, Q, B, + Context extends RequestHandlerContext = RequestHandlerContext, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory >( - handler: RequestHandler -) => RequestHandler; + handler: RequestHandler +) => RequestHandler; diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index fdcc7a87082ae..262ac3eb49f93 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -21,13 +21,13 @@ import { OnPostAuthHandler } from './lifecycle/on_post_auth'; import { OnPreResponseHandler } from './lifecycle/on_pre_response'; import { IBasePath } from './base_path_service'; import { ExternalUrlConfig } from '../external_url'; -import { PluginOpaqueId, RequestHandlerContext } from '..'; +import type { PluginOpaqueId, RequestHandlerContext } from '..'; /** * An object that handles registration of http request context providers. * @public */ -export type RequestHandlerContextContainer = IContextContainer>; +export type RequestHandlerContextContainer = IContextContainer; /** * Context provider for request handler. @@ -36,8 +36,9 @@ export type RequestHandlerContextContainer = IContextContainer = IContextProvider, TContextName>; + Context extends RequestHandlerContext, + ContextName extends keyof Context +> = IContextProvider; /** * @public @@ -230,14 +231,19 @@ export interface HttpServiceSetup { * ``` * @public */ - createRouter: () => IRouter; + createRouter: < + Context extends RequestHandlerContext = RequestHandlerContext + >() => IRouter; /** * Register a context provider for a route handler. * @example * ```ts * // my-plugin.ts - * deps.http.registerRouteHandlerContext( + * interface MyRequestHandlerContext extends RequestHandlerContext { + * myApp: { search(id: string): Promise }; + * } + * deps.http.registerRouteHandlerContext( * 'myApp', * (context, req) => { * async function search (id: string) { @@ -248,6 +254,8 @@ export interface HttpServiceSetup { * ); * * // my-route-handler.ts + * import type { MyRequestHandlerContext } from './my-plugin.ts'; + * const router = createRouter(); * router.get({ path: '/', validate: false }, async (context, req, res) => { * const response = await context.myApp.search(...); * return res.ok(response); @@ -255,9 +263,12 @@ export interface HttpServiceSetup { * ``` * @public */ - registerRouteHandlerContext: ( - contextName: T, - provider: RequestHandlerContextProvider + registerRouteHandlerContext: < + Context extends RequestHandlerContext, + ContextName extends keyof Context + >( + contextName: ContextName, + provider: RequestHandlerContextProvider ) => RequestHandlerContextContainer; /** @@ -272,13 +283,19 @@ export interface InternalHttpServiceSetup auth: HttpServerSetup['auth']; server: HttpServerSetup['server']; externalUrl: ExternalUrlConfig; - createRouter: (path: string, plugin?: PluginOpaqueId) => IRouter; + createRouter: ( + path: string, + plugin?: PluginOpaqueId + ) => IRouter; registerStaticDir: (path: string, dirPath: string) => void; getAuthHeaders: GetAuthHeaders; - registerRouteHandlerContext: ( + registerRouteHandlerContext: < + Context extends RequestHandlerContext, + ContextName extends keyof Context + >( pluginOpaqueId: PluginOpaqueId, - contextName: T, - provider: RequestHandlerContextProvider + contextName: ContextName, + provider: RequestHandlerContextProvider ) => RequestHandlerContextContainer; } diff --git a/src/core/server/http_resources/http_resources_service.ts b/src/core/server/http_resources/http_resources_service.ts index b8e4580164424..916fef2624381 100644 --- a/src/core/server/http_resources/http_resources_service.ts +++ b/src/core/server/http_resources/http_resources_service.ts @@ -53,12 +53,12 @@ export class HttpResourcesService implements CoreService( + register: ( route: RouteConfig, - handler: HttpResourcesRequestHandler + handler: HttpResourcesRequestHandler ) => { return router.get(route, (context, request, response) => { - return handler(context, request, { + return handler(context as Context, request, { ...response, ...this.createResponseToolkit(deps, context, request, response), }); diff --git a/src/core/server/http_resources/types.ts b/src/core/server/http_resources/types.ts index 6b1374d2d0be7..152bdd18f0211 100644 --- a/src/core/server/http_resources/types.ts +++ b/src/core/server/http_resources/types.ts @@ -6,7 +6,8 @@ * Public License, v 1. */ -import { +import type { RequestHandlerContext } from 'src/core/server'; +import type { IRouter, RouteConfig, IKibanaResponse, @@ -72,13 +73,12 @@ export interface HttpResourcesServiceToolkit { * }); * @public */ -export type HttpResourcesRequestHandler

= RequestHandler< - P, - Q, - B, - 'get', - KibanaResponseFactory & HttpResourcesServiceToolkit ->; +export type HttpResourcesRequestHandler< + P = unknown, + Q = unknown, + B = unknown, + Context extends RequestHandlerContext = RequestHandlerContext +> = RequestHandler; /** * Allows to configure HTTP response parameters @@ -98,8 +98,8 @@ export interface InternalHttpResourcesSetup { */ export interface HttpResources { /** To register a route handler executing passed function to form response. */ - register: ( + register: ( route: RouteConfig, - handler: HttpResourcesRequestHandler + handler: HttpResourcesRequestHandler ) => void; } diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index ea68c32450647..02b2d0bdf073b 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -11,6 +11,7 @@ import { first, map, publishReplay, tap } from 'rxjs/operators'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { PathConfigType } from '@kbn/utils'; +import type { RequestHandlerContext } from 'src/core/server'; // @ts-expect-error legacy config class import { Config as LegacyConfigClass } from '../../../legacy/server/config'; import { CoreService } from '../../types'; @@ -18,7 +19,14 @@ import { Config } from '../config'; import { CoreContext } from '../core_context'; 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 { + BasePathProxyServer, + HttpConfig, + HttpConfigType, + config as httpConfig, + IRouter, + RequestHandlerContextProvider, +} from '../http'; import { Logger } from '../logging'; import { LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyConfig, LegacyVars } from './types'; import { ExternalUrlConfigType, config as externalUrlConfig } from '../external_url'; @@ -225,11 +233,15 @@ export class LegacyService implements CoreService { }, http: { createCookieSessionStorageFactory: setupDeps.core.http.createCookieSessionStorageFactory, - registerRouteHandlerContext: setupDeps.core.http.registerRouteHandlerContext.bind( - null, - this.legacyId - ), - createRouter: () => router, + registerRouteHandlerContext: < + Context extends RequestHandlerContext, + ContextName extends keyof Context + >( + contextName: ContextName, + provider: RequestHandlerContextProvider + ) => setupDeps.core.http.registerRouteHandlerContext(this.legacyId, contextName, provider), + createRouter: () => + router as IRouter, resources: setupDeps.core.httpResources.createRegistrar(router), registerOnPreRouting: setupDeps.core.http.registerOnPreRouting, registerOnPreAuth: setupDeps.core.http.registerOnPreAuth, diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 1731996b77452..5b0e2ee21a887 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -10,6 +10,7 @@ import { map, shareReplay } from 'rxjs/operators'; import { combineLatest } from 'rxjs'; import { PathConfigType, config as pathConfig } from '@kbn/utils'; import { pick, deepFreeze } from '@kbn/std'; +import type { RequestHandlerContext } from 'src/core/server'; import { CoreContext } from '../core_context'; import { PluginWrapper } from './plugin'; import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service'; @@ -24,6 +25,7 @@ import { ElasticsearchConfigType, config as elasticsearchConfig, } from '../elasticsearch/elasticsearch_config'; +import { IRouter, RequestHandlerContextProvider } from '../http'; import { SavedObjectsConfigType, savedObjectsConfig } from '../saved_objects/saved_objects_config'; import { CoreSetup, CoreStart } from '..'; @@ -149,11 +151,15 @@ export function createPluginSetupContext( }, http: { createCookieSessionStorageFactory: deps.http.createCookieSessionStorageFactory, - registerRouteHandlerContext: deps.http.registerRouteHandlerContext.bind( - null, - plugin.opaqueId - ), - createRouter: () => router, + registerRouteHandlerContext: < + Context extends RequestHandlerContext, + ContextName extends keyof Context + >( + contextName: ContextName, + provider: RequestHandlerContextProvider + ) => deps.http.registerRouteHandlerContext(plugin.opaqueId, contextName, provider), + createRouter: () => + router as IRouter, resources: deps.httpResources.createRegistrar(router), registerOnPreRouting: deps.http.registerOnPreRouting, registerOnPreAuth: deps.http.registerOnPreAuth, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d0ba6aa1900c7..50d4a5bf502d6 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -132,6 +132,7 @@ import { ReindexParams } from 'elasticsearch'; import { ReindexRethrottleParams } from 'elasticsearch'; import { RenderSearchTemplateParams } from 'elasticsearch'; import { Request } from '@hapi/hapi'; +import { RequestHandlerContext as RequestHandlerContext_2 } from 'src/core/server'; import { ResponseObject } from '@hapi/hapi'; import { ResponseToolkit } from '@hapi/hapi'; import { SchemaTypeError } from '@kbn/config-schema'; @@ -982,7 +983,7 @@ export interface HttpAuth { // @public export interface HttpResources { - register: (route: RouteConfig, handler: HttpResourcesRequestHandler) => void; + register: (route: RouteConfig, handler: HttpResourcesRequestHandler) => void; } // @public @@ -991,7 +992,7 @@ export interface HttpResourcesRenderOptions { } // @public -export type HttpResourcesRequestHandler

= RequestHandler; +export type HttpResourcesRequestHandler

= RequestHandler; // @public export type HttpResourcesResponseOptions = HttpResponseOptions; @@ -1027,7 +1028,7 @@ export interface HttpServiceSetup { auth: HttpAuth; basePath: IBasePath; createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => Promise>; - createRouter: () => IRouter; + createRouter: () => IRouter; csp: ICspConfig; getServerInfo: () => HttpServerInfo; registerAuth: (handler: AuthenticationHandler) => void; @@ -1035,7 +1036,7 @@ export interface HttpServiceSetup { registerOnPreAuth: (handler: OnPreAuthHandler) => void; registerOnPreResponse: (handler: OnPreResponseHandler) => void; registerOnPreRouting: (handler: OnPreRoutingHandler) => void; - registerRouteHandlerContext: (contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; + registerRouteHandlerContext: (contextName: ContextName, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; } // @public (undocumented) @@ -1061,15 +1062,13 @@ export interface IClusterClient { } // @public -export interface IContextContainer> { +export interface IContextContainer { createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; - registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; + registerContext(pluginOpaqueId: PluginOpaqueId, contextName: ContextName, provider: IContextProvider): this; } -// Warning: (ae-forgotten-export) The symbol "PartialExceptFor" needs to be exported by the entry point index.d.ts -// // @public -export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: PartialExceptFor, 'core'>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +export type IContextProvider = (context: Omit, ...rest: HandlerParameters) => Promise | Context[ContextName]; // @public export interface ICspConfig { @@ -1154,17 +1153,17 @@ export interface IRenderOptions { } // @public -export interface IRouter { - delete: RouteRegistrar<'delete'>; - get: RouteRegistrar<'get'>; +export interface IRouter { + delete: RouteRegistrar<'delete', Context>; + get: RouteRegistrar<'get', Context>; // Warning: (ae-forgotten-export) The symbol "RouterRoute" needs to be exported by the entry point index.d.ts // // @internal getRoutes: () => RouterRoute[]; handleLegacyErrors: RequestHandlerWrapper; - patch: RouteRegistrar<'patch'>; - post: RouteRegistrar<'post'>; - put: RouteRegistrar<'put'>; + patch: RouteRegistrar<'patch', Context>; + post: RouteRegistrar<'post', Context>; + put: RouteRegistrar<'put', Context>; routerPath: string; } @@ -1563,7 +1562,7 @@ export type LegacyElasticsearchClientConfig = Pick = (context: RequestHandlerContext, request: KibanaRequest, response: ResponseFactory) => IKibanaResponse | Promise>; +export type RequestHandler

= (context: Context, request: KibanaRequest, response: ResponseFactory) => IKibanaResponse | Promise>; // @public export interface RequestHandlerContext { @@ -1930,13 +1929,13 @@ export interface RequestHandlerContext { } // @public -export type RequestHandlerContextContainer = IContextContainer>; +export type RequestHandlerContextContainer = IContextContainer; // @public -export type RequestHandlerContextProvider = IContextProvider, TContextName>; +export type RequestHandlerContextProvider = IContextProvider; // @public -export type RequestHandlerWrapper = (handler: RequestHandler) => RequestHandler; +export type RequestHandlerWrapper = (handler: RequestHandler) => RequestHandler; // @public export interface ResolveCapabilitiesOptions { @@ -1989,7 +1988,7 @@ export type RouteContentType = 'application/json' | 'application/*+json' | 'appl export type RouteMethod = SafeRouteMethod | DestructiveRouteMethod; // @public -export type RouteRegistrar = (route: RouteConfig, handler: RequestHandler) => void; +export type RouteRegistrar = (route: RouteConfig, handler: RequestHandler) => void; // @public export class RouteValidationError extends SchemaTypeError { diff --git a/src/core/server/server.ts b/src/core/server/server.ts index a58aae7e0ff26..60f3f90428d40 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -294,7 +294,7 @@ export class Server { coreSetup.http.registerRouteHandlerContext( coreId, 'core', - async (context, req, res): Promise => { + (context, req, res): RequestHandlerContext['core'] => { return new CoreRouteHandlerContext(this.coreStart!, req); } ); diff --git a/src/core/utils/context.test.ts b/src/core/utils/context.test.ts deleted file mode 100644 index 42cb8d635ae02..0000000000000 --- a/src/core/utils/context.test.ts +++ /dev/null @@ -1,268 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { ContextContainer } from './context'; -import { PluginOpaqueId } from '../server'; - -const pluginA = Symbol('pluginA'); -const pluginB = Symbol('pluginB'); -const pluginC = Symbol('pluginC'); -const pluginD = Symbol('pluginD'); -const plugins: ReadonlyMap = new Map([ - [pluginA, []], - [pluginB, [pluginA]], - [pluginC, [pluginA, pluginB]], - [pluginD, []], -]); - -interface MyContext { - core1: string; - core2: number; - ctxFromA: string; - ctxFromB: number; - ctxFromC: boolean; - ctxFromD: object; -} - -const coreId = Symbol(); - -describe('ContextContainer', () => { - it('does not allow the same context to be registered twice', () => { - const contextContainer = new ContextContainer<(context: MyContext) => string>(plugins, coreId); - contextContainer.registerContext(coreId, 'ctxFromA', () => 'aString'); - - expect(() => - contextContainer.registerContext(coreId, 'ctxFromA', () => 'aString') - ).toThrowErrorMatchingInlineSnapshot( - `"Context provider for ctxFromA has already been registered."` - ); - }); - - describe('registerContext', () => { - it('throws error if called with an unknown symbol', async () => { - const contextContainer = new ContextContainer<(context: MyContext) => string>( - plugins, - coreId - ); - await expect(() => - contextContainer.registerContext(Symbol('unknown'), 'ctxFromA', jest.fn()) - ).toThrowErrorMatchingInlineSnapshot( - `"Cannot register context for unknown plugin: Symbol(unknown)"` - ); - }); - }); - - describe('context building', () => { - it('resolves dependencies', async () => { - const contextContainer = new ContextContainer<(context: MyContext) => string>( - plugins, - coreId - ); - expect.assertions(8); - contextContainer.registerContext(coreId, 'core1', (context) => { - expect(context).toEqual({}); - return 'core'; - }); - - contextContainer.registerContext(pluginA, 'ctxFromA', (context) => { - expect(context).toEqual({ core1: 'core' }); - return 'aString'; - }); - contextContainer.registerContext(pluginB, 'ctxFromB', (context) => { - expect(context).toEqual({ core1: 'core', ctxFromA: 'aString' }); - return 299; - }); - contextContainer.registerContext(pluginC, 'ctxFromC', (context) => { - expect(context).toEqual({ core1: 'core', ctxFromA: 'aString', ctxFromB: 299 }); - return false; - }); - contextContainer.registerContext(pluginD, 'ctxFromD', (context) => { - expect(context).toEqual({ core1: 'core' }); - return {}; - }); - - const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(pluginC, rawHandler1); - - const rawHandler2 = jest.fn(() => 'handler2'); - const handler2 = contextContainer.createHandler(pluginD, rawHandler2); - - await handler1(); - await handler2(); - - // Should have context from pluginC, its deps, and core - expect(rawHandler1).toHaveBeenCalledWith({ - core1: 'core', - ctxFromA: 'aString', - ctxFromB: 299, - ctxFromC: false, - }); - - // Should have context from pluginD, and core - expect(rawHandler2).toHaveBeenCalledWith({ - core1: 'core', - ctxFromD: {}, - }); - }); - - it('exposes all core context to all providers regardless of registration order', async () => { - expect.assertions(4); - - const contextContainer = new ContextContainer<(context: MyContext) => string>( - plugins, - coreId - ); - contextContainer - .registerContext(pluginA, 'ctxFromA', (context) => { - expect(context).toEqual({ core1: 'core', core2: 101 }); - return `aString ${context.core1} ${context.core2}`; - }) - .registerContext(coreId, 'core1', () => 'core') - .registerContext(coreId, 'core2', () => 101) - .registerContext(pluginB, 'ctxFromB', (context) => { - expect(context).toEqual({ core1: 'core', core2: 101, ctxFromA: 'aString core 101' }); - return 277; - }); - - const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(pluginB, rawHandler1); - - expect(await handler1()).toEqual('handler1'); - - expect(rawHandler1).toHaveBeenCalledWith({ - core1: 'core', - core2: 101, - ctxFromA: 'aString core 101', - ctxFromB: 277, - }); - }); - - it('exposes all core context to core providers', async () => { - expect.assertions(4); - const contextContainer = new ContextContainer<(context: MyContext) => string>( - plugins, - coreId - ); - - contextContainer - .registerContext(coreId, 'core1', (context) => { - expect(context).toEqual({}); - return 'core'; - }) - .registerContext(coreId, 'core2', (context) => { - expect(context).toEqual({ core1: 'core' }); - return 101; - }); - - const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(pluginA, rawHandler1); - - expect(await handler1()).toEqual('handler1'); - - // If no context is registered for pluginA, only core contexts should be exposed - expect(rawHandler1).toHaveBeenCalledWith({ - core1: 'core', - core2: 101, - }); - }); - - it('does not expose plugin contexts to core handler', async () => { - const contextContainer = new ContextContainer<(context: MyContext) => string>( - plugins, - coreId - ); - - contextContainer - .registerContext(coreId, 'core1', (context) => 'core') - .registerContext(pluginA, 'ctxFromA', (context) => 'aString'); - - const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(coreId, rawHandler1); - - expect(await handler1()).toEqual('handler1'); - // pluginA context should not be present in a core handler - expect(rawHandler1).toHaveBeenCalledWith({ - core1: 'core', - }); - }); - - it('passes additional arguments to providers', async () => { - expect.assertions(6); - const contextContainer = new ContextContainer< - (context: MyContext, arg1: string, arg2: number) => string - >(plugins, coreId); - - contextContainer.registerContext(coreId, 'core1', (context, str, num) => { - expect(str).toEqual('passed string'); - expect(num).toEqual(77); - return `core ${str}`; - }); - - contextContainer.registerContext(pluginD, 'ctxFromD', (context, str, num) => { - expect(str).toEqual('passed string'); - expect(num).toEqual(77); - return { - num: 77, - }; - }); - - const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(pluginD, rawHandler1); - - expect(await handler1('passed string', 77)).toEqual('handler1'); - - expect(rawHandler1).toHaveBeenCalledWith( - { - core1: 'core passed string', - ctxFromD: { - num: 77, - }, - }, - 'passed string', - 77 - ); - }); - }); - - describe('createHandler', () => { - it('throws error if called with an unknown symbol', async () => { - const contextContainer = new ContextContainer<(context: MyContext) => string>( - plugins, - coreId - ); - await expect(() => - contextContainer.createHandler(Symbol('unknown'), jest.fn()) - ).toThrowErrorMatchingInlineSnapshot( - `"Cannot create handler for unknown plugin: Symbol(unknown)"` - ); - }); - - it('returns value from original handler', async () => { - const contextContainer = new ContextContainer<(context: MyContext) => string>( - plugins, - coreId - ); - const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(pluginA, rawHandler1); - - expect(await handler1()).toEqual('handler1'); - }); - - it('passes additional arguments to handlers', async () => { - const contextContainer = new ContextContainer< - (context: MyContext, arg1: string, arg2: number) => string - >(plugins, coreId); - - const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(pluginA, rawHandler1); - - await handler1('passed string', 77); - expect(rawHandler1).toHaveBeenCalledWith({}, 'passed string', 77); - }); - }); -}); diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts index 0a480606b9990..d365b15866c3f 100644 --- a/src/core/utils/index.ts +++ b/src/core/utils/index.ts @@ -6,12 +6,4 @@ * Public License, v 1. */ -export { - ContextContainer, - HandlerContextType, - HandlerFunction, - HandlerParameters, - IContextContainer, - IContextProvider, -} from './context'; export { DEFAULT_APP_CATEGORIES } from './default_app_categories'; diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts index 2613104312ba5..55eadf5212f3a 100644 --- a/src/plugins/bfetch/server/plugin.ts +++ b/src/plugins/bfetch/server/plugin.ts @@ -6,7 +6,7 @@ * Public License, v 1. */ -import { +import type { CoreStart, PluginInitializerContext, CoreSetup, @@ -15,6 +15,7 @@ import { KibanaRequest, RouteMethod, RequestHandler, + RequestHandlerContext, } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { Subject } from 'rxjs'; @@ -77,9 +78,16 @@ export interface BfetchServerSetup { * * @param streamHandler */ - createStreamingRequestHandler: ( + createStreamingRequestHandler: < + Response, + P, + Q, + B, + Context extends RequestHandlerContext = RequestHandlerContext, + Method extends RouteMethod = any + >( streamHandler: StreamingRequestHandler - ) => RequestHandler; + ) => RequestHandler; } // eslint-disable-next-line diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 34b4dc9116302..048d60dbc25c6 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2369,7 +2369,7 @@ export class SearchSource { removeField(field: K): this; serialize(): { searchSourceJSON: string; - references: import("src/core/server").SavedObjectReference[]; + references: import("../../../../../core/types").SavedObjectReference[]; }; setField(field: K, value: SearchSourceFields[K]): this; setFields(newFields: SearchSourceFields): this; diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 72eb0015215d1..27af11674d061 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -235,6 +235,8 @@ export { SearchUsage, SessionService, ISessionService, + DataApiRequestHandlerContext, + DataRequestHandlerContext, } from './search'; // Search namespace diff --git a/src/plugins/data/server/index_patterns/routes/util/handle_errors.ts b/src/plugins/data/server/index_patterns/routes/util/handle_errors.ts index bede9165de08c..9b4268cfd53c9 100644 --- a/src/plugins/data/server/index_patterns/routes/util/handle_errors.ts +++ b/src/plugins/data/server/index_patterns/routes/util/handle_errors.ts @@ -6,7 +6,7 @@ * Public License, v 1. */ -import { RequestHandler, RouteMethod } from 'src/core/server'; +import type { RequestHandler, RouteMethod, RequestHandlerContext } from 'src/core/server'; import { ErrorIndexPatternNotFound } from '../../error'; interface ErrorResponseBody { @@ -29,9 +29,15 @@ interface ErrorWithData { * } * ``` */ -export const handleErrors = ( - handler: RequestHandler -): RequestHandler => async (context, request, response) => { +export const handleErrors = < + P, + Q, + B, + Context extends RequestHandlerContext, + Method extends RouteMethod +>( + handler: RequestHandler +): RequestHandler => async (context, request, response) => { try { return await handler(context, request, response); } catch (error) { diff --git a/src/plugins/data/server/search/routes/msearch.ts b/src/plugins/data/server/search/routes/msearch.ts index 5b62d6732430f..70bb56afa4403 100644 --- a/src/plugins/data/server/search/routes/msearch.ts +++ b/src/plugins/data/server/search/routes/msearch.ts @@ -8,12 +8,11 @@ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; import { SearchRouteDependencies } from '../search_service'; import { getCallMsearch } from './call_msearch'; import { reportServerError } from '../../../../kibana_utils/server'; - +import type { DataPluginRouter } from '../types'; /** * The msearch route takes in an array of searches, each consisting of header * and body json, and reformts them into a single request for the _msearch API. @@ -27,7 +26,10 @@ import { reportServerError } from '../../../../kibana_utils/server'; * * @deprecated */ -export function registerMsearchRoute(router: IRouter, deps: SearchRouteDependencies): void { +export function registerMsearchRoute( + router: DataPluginRouter, + deps: SearchRouteDependencies +): void { router.post( { path: '/internal/_msearch', diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index 5969dd1324376..6d2da4c1e63dd 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -8,12 +8,12 @@ import { first } from 'rxjs/operators'; import { schema } from '@kbn/config-schema'; -import type { IRouter } from 'src/core/server'; import { getRequestAbortedSignal } from '../../lib'; import { shimHitsTotal } from './shim_hits_total'; import { reportServerError } from '../../../../kibana_utils/server'; +import type { DataPluginRouter } from '../types'; -export function registerSearchRoute(router: IRouter): void { +export function registerSearchRoute(router: DataPluginRouter): void { router.post( { path: '/internal/search/{strategy}/{id?}', diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 762b1bffc7c11..f1a6fc09ee21f 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -21,12 +21,13 @@ import { import { catchError, first, map } from 'rxjs/operators'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; -import { +import type { ISearchSetup, ISearchStart, ISearchStrategy, SearchEnhancements, SearchStrategyDependencies, + DataRequestHandlerContext, } from './types'; import { AggsService } from './aggs'; @@ -64,12 +65,6 @@ import { ConfigSchema } from '../../config'; import { SessionService, IScopedSessionService, ISessionService } from './session'; import { KbnServerError } from '../../../kibana_utils/server'; -declare module 'src/core/server' { - interface RequestHandlerContext { - search?: ISearchClient & { session: IScopedSessionService }; - } -} - type StrategyMap = Record>; /** @internal */ @@ -112,7 +107,7 @@ export class SearchService implements Plugin { ): ISearchSetup { const usage = usageCollection ? usageProvider(core) : undefined; - const router = core.http.createRouter(); + const router = core.http.createRouter(); const routeDependencies = { getStartServices: core.getStartServices, globalConfig$: this.initializerContext.config.legacy.globalConfig$, @@ -124,11 +119,14 @@ export class SearchService implements Plugin { this.coreStart = coreStart; }); - core.http.registerRouteHandlerContext('search', async (context, request) => { - const search = this.asScopedProvider(this.coreStart!)(request); - const session = this.sessionService.asScopedProvider(this.coreStart!)(request); - return { ...search, session }; - }); + core.http.registerRouteHandlerContext( + 'search', + async (context, request) => { + const search = this.asScopedProvider(this.coreStart!)(request); + const session = this.sessionService.asScopedProvider(this.coreStart!)(request); + return { ...search, session }; + } + ); this.registerSearchStrategy( ES_SEARCH_STRATEGY, diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index d292282f8a435..7e7d22fdb2be1 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -7,11 +7,13 @@ */ import { Observable } from 'rxjs'; -import { +import type { + IRouter, IScopedClusterClient, IUiSettingsClient, SavedObjectsClientContract, KibanaRequest, + RequestHandlerContext, } from 'src/core/server'; import { ISearchOptions, @@ -23,7 +25,7 @@ import { import { AggsSetup, AggsStart } from './aggs'; import { SearchUsage } from './collectors'; import { IEsSearchRequest, IEsSearchResponse } from './es_search'; -import { ISessionService } from './session'; +import { ISessionService, IScopedSessionService } from './session'; export interface SearchEnhancements { defaultStrategy: string; @@ -101,3 +103,16 @@ export interface ISearchStart< asScoped: (request: KibanaRequest) => Promise; }; } + +export interface DataApiRequestHandlerContext extends ISearchClient { + session: IScopedSessionService; +} + +/** + * @internal + */ +export interface DataRequestHandlerContext extends RequestHandlerContext { + search: DataApiRequestHandlerContext; +} + +export type DataPluginRouter = IRouter; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 84a82511d5a5e..ef8015ecaca26 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -53,6 +53,7 @@ import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core import { PublicMethodsOf } from '@kbn/utility-types'; import { RecursiveReadonly } from '@kbn/utility-types'; import { RequestAdapter } from 'src/plugins/inspector/common'; +import { RequestHandlerContext } from 'src/core/server'; import { RequestStatistics } from 'src/plugins/inspector/common'; import { SavedObject } from 'src/core/server'; import { SavedObjectsClientContract } from 'src/core/server'; @@ -304,6 +305,23 @@ export const castEsToKbnFieldTypeName: (esType: ES_FIELD_TYPES | string) => KBN_ // @public (undocumented) export const config: PluginConfigDescriptor; +// Warning: (ae-forgotten-export) The symbol "ISearchClient" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "DataApiRequestHandlerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DataApiRequestHandlerContext extends ISearchClient { + // Warning: (ae-forgotten-export) The symbol "IScopedSessionService" needs to be exported by the entry point index.d.ts + // + // (undocumented) + session: IScopedSessionService; +} + +// @internal (undocumented) +export interface DataRequestHandlerContext extends RequestHandlerContext { + // (undocumented) + search: DataApiRequestHandlerContext; +} + // @public (undocumented) export enum ES_FIELD_TYPES { // (undocumented) @@ -923,8 +941,6 @@ export interface ISearchStart ISearchClient; getSearchStrategy: (name?: string) => ISearchStrategy; @@ -950,8 +966,6 @@ export interface ISearchStrategy (request: KibanaRequest) => IScopedSessionService; } @@ -1115,10 +1129,10 @@ export class Plugin implements Plugin_2 Promise; + fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -1405,23 +1419,23 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:100:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:126:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:126:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:243:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:244:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:253:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:254:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:259:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:260:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:264:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:267:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:268:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:245:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:246:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:256:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:257:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:261:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:269:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:270:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index_patterns/index_patterns_service.ts:59:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:79:74 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/search/types.ts:101:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/search/types.ts:103:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.test.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.test.ts index 457052decd65b..ff73261d173d7 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.test.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.test.ts @@ -36,7 +36,7 @@ describe('sendTelemetryOptInStatus', () => { expect(fetch).toBeCalledTimes(1); expect(fetch).toBeCalledWith(mockConfig.optInStatusUrl, { method: 'post', - body: mockOptInStatus, + body: '["mock_opt_in_hashed_value"]', headers: { 'X-Elastic-Stack-Version': mockConfig.currentKibanaVersion }, }); }); diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts index 33648769f170a..1d23a49fc33ed 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts @@ -6,7 +6,6 @@ * Public License, v 1. */ -// @ts-ignore import fetch from 'node-fetch'; import { IRouter } from 'kibana/server'; @@ -35,7 +34,7 @@ export async function sendTelemetryOptInStatus( await fetch(optInStatusUrl, { method: 'post', - body: optInStatus, + body: JSON.stringify(optInStatus), headers: { 'X-Elastic-Stack-Version': currentKibanaVersion }, }); } diff --git a/src/plugins/vis_type_timelion/server/plugin.ts b/src/plugins/vis_type_timelion/server/plugin.ts index b41eecfc0c63a..f999c1dfc773a 100644 --- a/src/plugins/vis_type_timelion/server/plugin.ts +++ b/src/plugins/vis_type_timelion/server/plugin.ts @@ -11,8 +11,12 @@ import { first } from 'rxjs/operators'; import { TypeOf, schema } from '@kbn/config-schema'; import { RecursiveReadonly } from '@kbn/utility-types'; import { deepFreeze } from '@kbn/std'; +import type { RequestHandlerContext } from 'src/core/server'; -import { PluginStart } from '../../../../src/plugins/data/server'; +import type { + PluginStart, + DataApiRequestHandlerContext, +} from '../../../../src/plugins/data/server'; import { CoreSetup, PluginInitializerContext } from '../../../../src/core/server'; import { configSchema } from '../config'; import loadFunctions from './lib/load_functions'; @@ -67,7 +71,9 @@ export class Plugin { const logger = this.initializerContext.logger.get('timelion'); - const router = core.http.createRouter(); + const router = core.http.createRouter< + RequestHandlerContext & { search: DataApiRequestHandlerContext } + >(); const deps = { configManager, @@ -79,7 +85,7 @@ export class Plugin { functionsRoute(router, deps); runRoute(router, deps); - validateEsRoute(router, core); + validateEsRoute(router); core.uiSettings.register({ 'timelion:es.timefield': { diff --git a/src/plugins/vis_type_timelion/server/routes/validate_es.ts b/src/plugins/vis_type_timelion/server/routes/validate_es.ts index 242029a492e92..1637fcc464f46 100644 --- a/src/plugins/vis_type_timelion/server/routes/validate_es.ts +++ b/src/plugins/vis_type_timelion/server/routes/validate_es.ts @@ -7,9 +7,12 @@ */ import _ from 'lodash'; -import { IRouter, CoreSetup } from 'kibana/server'; +import { IRouter, RequestHandlerContext } from 'kibana/server'; +import type { DataApiRequestHandlerContext } from '../../../data/server'; -export function validateEsRoute(router: IRouter, core: CoreSetup) { +export function validateEsRoute( + router: IRouter +) { router.get( { path: '/api/timelion/validate/es', diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts index 8bcafa685a275..dc075930cf256 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts @@ -8,14 +8,15 @@ import { uniqBy } from 'lodash'; import { first, map } from 'rxjs/operators'; -import { KibanaRequest, RequestHandlerContext } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { Framework } from '../plugin'; import { IndexPatternsFetcher } from '../../../data/server'; import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy'; +import { VisTypeTimeseriesRequestHandlerContext } from '../types'; export async function getFields( - requestContext: RequestHandlerContext, + requestContext: VisTypeTimeseriesRequestHandlerContext, request: KibanaRequest, framework: Framework, indexPatternString: string diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts index 81881d5bda4ef..3b1e9d373f136 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts @@ -6,7 +6,7 @@ * Public License, v 1. */ -import { FakeRequest, RequestHandlerContext } from 'kibana/server'; +import { FakeRequest } from 'kibana/server'; import _ from 'lodash'; import { first, map } from 'rxjs/operators'; @@ -15,6 +15,7 @@ import { getPanelData } from './vis_data/get_panel_data'; import { Framework } from '../plugin'; import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy'; import { TimeseriesVisData } from '../../common/types'; +import type { VisTypeTimeseriesRequestHandlerContext } from '../types'; export interface GetVisDataOptions { timerange: { @@ -30,13 +31,13 @@ export interface GetVisDataOptions { } export type GetVisData = ( - requestContext: RequestHandlerContext, + requestContext: VisTypeTimeseriesRequestHandlerContext, options: GetVisDataOptions, framework: Framework ) => Promise; export function getVisData( - requestContext: RequestHandlerContext, + requestContext: VisTypeTimeseriesRequestHandlerContext, request: FakeRequest & { body: GetVisDataOptions }, framework: Framework ): Promise { diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index 871baf959645d..966daca87a208 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -6,12 +6,7 @@ * Public License, v 1. */ -import type { - RequestHandlerContext, - FakeRequest, - IUiSettingsClient, - SavedObjectsClientContract, -} from 'kibana/server'; +import type { FakeRequest, IUiSettingsClient, SavedObjectsClientContract } from 'kibana/server'; import type { Framework } from '../../../plugin'; import type { IndexPatternsFetcher, IFieldType } from '../../../../../data/server'; @@ -19,6 +14,7 @@ import type { VisPayload } from '../../../../common/types'; import type { IndexPatternsService } from '../../../../../data/common'; import { indexPatterns } from '../../../../../data/server'; import { SanitizedFieldType } from '../../../../common/types'; +import type { VisTypeTimeseriesRequestHandlerContext } from '../../../types'; /** * ReqFacade is a regular KibanaRequest object extended with additional service @@ -27,7 +23,7 @@ import { SanitizedFieldType } from '../../../../common/types'; * This will be replaced by standard KibanaRequest and RequestContext objects in a later version. */ export interface ReqFacade extends FakeRequest { - requestContext: RequestHandlerContext; + requestContext: VisTypeTimeseriesRequestHandlerContext; framework: Framework; payload: T; pre: { @@ -58,8 +54,8 @@ export abstract class AbstractSearchStrategy { bodies.forEach((body) => { requests.push( - req.requestContext - .search!.search( + req.requestContext.search + .search( { indexType, params: { diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts index bd483e3f0f72e..adcd7e8bbf0d5 100644 --- a/src/plugins/vis_type_timeseries/server/plugin.ts +++ b/src/plugins/vis_type_timeseries/server/plugin.ts @@ -11,9 +11,7 @@ import { CoreSetup, CoreStart, Plugin, - RequestHandlerContext, Logger, - IRouter, FakeRequest, } from 'src/core/server'; import { Observable } from 'rxjs'; @@ -27,6 +25,7 @@ import { visDataRoutes } from './routes/vis'; import { fieldsRoutes } from './routes/fields'; import { SearchStrategyRegistry } from './lib/search_strategies'; import { uiSettings } from './ui_settings'; +import type { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesRouter } from './types'; export interface LegacySetup { server: Server; @@ -42,7 +41,7 @@ interface VisTypeTimeseriesPluginStartDependencies { export interface VisTypeTimeseriesSetup { getVisData: ( - requestContext: RequestHandlerContext, + requestContext: VisTypeTimeseriesRequestHandlerContext, fakeRequest: FakeRequest, options: GetVisDataOptions ) => ReturnType; @@ -55,7 +54,7 @@ export interface Framework { config$: Observable; globalConfig$: PluginInitializerContext['config']['legacy']['globalConfig$']; logger: Logger; - router: IRouter; + router: VisTypeTimeseriesRouter; searchStrategyRegistry: SearchStrategyRegistry; } @@ -73,7 +72,7 @@ export class VisTypeTimeseriesPlugin implements Plugin { const config$ = this.initializerContext.config.create(); // Global config contains things like the ES shard timeout const globalConfig$ = this.initializerContext.config.legacy.globalConfig$; - const router = core.http.createRouter(); + const router = core.http.createRouter(); const searchStrategyRegistry = new SearchStrategyRegistry(); @@ -92,7 +91,7 @@ export class VisTypeTimeseriesPlugin implements Plugin { return { getVisData: async ( - requestContext: RequestHandlerContext, + requestContext: VisTypeTimeseriesRequestHandlerContext, fakeRequest: FakeRequest, options: GetVisDataOptions ) => { diff --git a/src/plugins/vis_type_timeseries/server/routes/vis.ts b/src/plugins/vis_type_timeseries/server/routes/vis.ts index fd9dfc18eadff..d1fcaa97b3053 100644 --- a/src/plugins/vis_type_timeseries/server/routes/vis.ts +++ b/src/plugins/vis_type_timeseries/server/routes/vis.ts @@ -6,17 +6,18 @@ * Public License, v 1. */ -import { IRouter, KibanaRequest } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { schema } from '@kbn/config-schema'; import { ensureNoUnsafeProperties } from '@kbn/std'; import { getVisData, GetVisDataOptions } from '../lib/get_vis_data'; import { visPayloadSchema } from '../../common/vis_schema'; import { ROUTES } from '../../common/constants'; import { Framework } from '../plugin'; +import type { VisTypeTimeseriesRouter } from '../types'; const escapeHatch = schema.object({}, { unknowns: 'allow' }); -export const visDataRoutes = (router: IRouter, framework: Framework) => { +export const visDataRoutes = (router: VisTypeTimeseriesRouter, framework: Framework) => { router.post( { path: ROUTES.VIS_DATA, diff --git a/src/plugins/vis_type_timeseries/server/types.ts b/src/plugins/vis_type_timeseries/server/types.ts new file mode 100644 index 0000000000000..29cd33031c883 --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/types.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { DataApiRequestHandlerContext } from '../../data/server'; + +export interface VisTypeTimeseriesRequestHandlerContext extends RequestHandlerContext { + search: DataApiRequestHandlerContext; +} + +export type VisTypeTimeseriesRouter = IRouter; diff --git a/test/plugin_functional/plugins/core_plugin_a/server/index.ts b/test/plugin_functional/plugins/core_plugin_a/server/index.ts index 2134b88eae84a..f98b5772c7e5f 100644 --- a/test/plugin_functional/plugins/core_plugin_a/server/index.ts +++ b/test/plugin_functional/plugins/core_plugin_a/server/index.ts @@ -7,6 +7,6 @@ */ import { CorePluginAPlugin } from './plugin'; -export { PluginARequestContext } from './plugin'; +export { PluginAApiRequestContext } from './plugin'; export const plugin = () => new CorePluginAPlugin(); diff --git a/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts index b49336f2d3e1f..367168afdc1de 100644 --- a/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts @@ -6,26 +6,27 @@ * Public License, v 1. */ -import { Plugin, CoreSetup } from 'kibana/server'; +import type { Plugin, CoreSetup, RequestHandlerContext } from 'kibana/server'; -export interface PluginARequestContext { +export interface PluginAApiRequestContext { ping: () => Promise; } -declare module 'kibana/server' { - interface RequestHandlerContext { - pluginA?: PluginARequestContext; - } +interface PluginARequstHandlerContext extends RequestHandlerContext { + pluginA: PluginAApiRequestContext; } export class CorePluginAPlugin implements Plugin { public setup(core: CoreSetup, deps: {}) { - core.http.registerRouteHandlerContext('pluginA', (context) => { - return { - ping: () => - context.core.elasticsearch.legacy.client.callAsInternalUser('ping') as Promise, - }; - }); + core.http.registerRouteHandlerContext( + 'pluginA', + (context) => { + return { + ping: () => + context.core.elasticsearch.legacy.client.callAsInternalUser('ping') as Promise, + }; + } + ); } public start() {} diff --git a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts index 528f996915885..fba19a46fc7e9 100644 --- a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts @@ -6,19 +6,17 @@ * Public License, v 1. */ -import { Plugin, CoreSetup } from 'kibana/server'; +import { Plugin, CoreSetup, RequestHandlerContext } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import { PluginARequestContext } from '../../core_plugin_a/server'; +import { PluginAApiRequestContext } from '../../core_plugin_a/server'; -declare module 'kibana/server' { - interface RequestHandlerContext { - pluginA?: PluginARequestContext; - } +interface PluginBContext extends RequestHandlerContext { + pluginA: PluginAApiRequestContext; } export class CorePluginBPlugin implements Plugin { public setup(core: CoreSetup, deps: {}) { - const router = core.http.createRouter(); + const router = core.http.createRouter(); router.get({ path: '/core_plugin_b', validate: false }, async (context, req, res) => { if (!context.pluginA) return res.internalError({ body: 'pluginA is disabled' }); const response = await context.pluginA.ping(); diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts index 6569d89f9d0b6..5b58c308d5097 100644 --- a/test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts +++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts @@ -7,6 +7,5 @@ */ import { CorePluginRouteTimeoutsPlugin } from './plugin'; -export { PluginARequestContext } from './plugin'; export const plugin = () => new CorePluginRouteTimeoutsPlugin(); diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts index e9e97288a1156..a6d964fdeb7d4 100644 --- a/test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts @@ -9,16 +9,6 @@ import { Plugin, CoreSetup } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -export interface PluginARequestContext { - ping: () => Promise; -} - -declare module 'kibana/server' { - interface RequestHandlerContext { - pluginA?: PluginARequestContext; - } -} - export class CorePluginRouteTimeoutsPlugin implements Plugin { public setup(core: CoreSetup, deps: {}) { const { http } = core; diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index c43cc20bd4773..6c4857bff4e81 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -14,12 +14,13 @@ import { ActionsConfigType } from './types'; export type ActionsClient = PublicMethodsOf; export type ActionsAuthorization = PublicMethodsOf; -export { +export type { ActionsPlugin, ActionResult, ActionTypeExecutorOptions, ActionType, PreConfiguredAction, + ActionsApiRequestHandlerContext, } from './types'; export type { @@ -45,7 +46,7 @@ export type { TeamsActionParams, } from './builtin_action_types'; -export { PluginSetupContract, PluginStartContract } from './plugin'; +export type { PluginSetupContract, PluginStartContract } from './plugin'; export { asSavedObjectExecutionSource, asHttpRequestExecutionSource } from './lib'; diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index ff43b05b6d895..d1e40563c0172 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -12,7 +12,7 @@ import { featuresPluginMock } from '../../features/server/mocks'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; import { eventLogMock } from '../../event_log/server/mocks'; -import { ActionType } from './types'; +import { ActionType, ActionsApiRequestHandlerContext } from './types'; import { ActionsConfig } from './config'; import { ActionsPlugin, @@ -73,7 +73,10 @@ describe('Actions Plugin', () => { }); expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1); - const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0]; + const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0] as [ + string, + Function + ]; expect(handler[0]).toEqual('actions'); const actionsContextHandler = ((await handler[1]( @@ -91,7 +94,7 @@ describe('Actions Plugin', () => { } as unknown) as RequestHandlerContext, httpServerMock.createKibanaRequest(), httpServerMock.createResponseFactory() - )) as unknown) as RequestHandlerContext['actions']; + )) as unknown) as ActionsApiRequestHandlerContext; actionsContextHandler!.getActionsClient(); }); @@ -101,7 +104,10 @@ describe('Actions Plugin', () => { await plugin.setup(coreSetup as any, pluginsSetup); expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1); - const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0]; + const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0] as [ + string, + Function + ]; expect(handler[0]).toEqual('actions'); const actionsContextHandler = ((await handler[1]( @@ -114,7 +120,7 @@ describe('Actions Plugin', () => { } as unknown) as RequestHandlerContext, httpServerMock.createKibanaRequest(), httpServerMock.createResponseFactory() - )) as unknown) as RequestHandlerContext['actions']; + )) as unknown) as ActionsApiRequestHandlerContext; expect(() => actionsContextHandler!.getActionsClient()).toThrowErrorMatchingInlineSnapshot( `"Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."` ); diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 133e5f9c6aa2c..1543f8d7a07ce 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -14,7 +14,6 @@ import { CoreStart, KibanaRequest, Logger, - RequestHandler, IContextProvider, ElasticsearchServiceStart, ILegacyClusterClient, @@ -46,6 +45,7 @@ import { ActionTypeConfig, ActionTypeSecrets, ActionTypeParams, + ActionsRequestHandlerContext, } from './types'; import { getActionsConfigurationUtilities } from './actions_config'; @@ -228,7 +228,7 @@ export class ActionsPlugin implements Plugin, Plugi } this.kibanaIndexConfig.subscribe((config) => { - core.http.registerRouteHandlerContext( + core.http.registerRouteHandlerContext( 'actions', this.createRouteHandlerContext(core, config.kibana.index) ); @@ -243,7 +243,7 @@ export class ActionsPlugin implements Plugin, Plugi }); // Routes - const router = core.http.createRouter(); + const router = core.http.createRouter(); createActionRoute(router, this.licenseState); deleteActionRoute(router, this.licenseState); getActionRoute(router, this.licenseState); @@ -448,7 +448,7 @@ export class ActionsPlugin implements Plugin, Plugi private createRouteHandlerContext = ( core: CoreSetup, defaultKibanaIndex: string - ): IContextProvider, 'actions'> => { + ): IContextProvider => { const { actionTypeRegistry, isESOUsingEphemeralEncryptionKey, diff --git a/x-pack/plugins/actions/server/routes/create.ts b/x-pack/plugins/actions/server/routes/create.ts index 462d3f42b506c..3400568c7dc4c 100644 --- a/x-pack/plugins/actions/server/routes/create.ts +++ b/x-pack/plugins/actions/server/routes/create.ts @@ -4,15 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; -import { ActionResult } from '../types'; +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; +import { ActionResult, ActionsRequestHandlerContext } from '../types'; import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; @@ -23,7 +17,10 @@ export const bodySchema = schema.object({ secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), }); -export const createActionRoute = (router: IRouter, licenseState: ILicenseState) => { +export const createActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { router.post( { path: `${BASE_ACTION_API_PATH}/action`, @@ -31,11 +28,7 @@ export const createActionRoute = (router: IRouter, licenseState: ILicenseState) body: bodySchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.actions) { diff --git a/x-pack/plugins/actions/server/routes/delete.ts b/x-pack/plugins/actions/server/routes/delete.ts index a7303247e95b0..7183ebb7c8233 100644 --- a/x-pack/plugins/actions/server/routes/delete.ts +++ b/x-pack/plugins/actions/server/routes/delete.ts @@ -9,22 +9,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; +import { ActionsRequestHandlerContext } from '../types'; const paramSchema = schema.object({ id: schema.string(), }); -export const deleteActionRoute = (router: IRouter, licenseState: ILicenseState) => { +export const deleteActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { router.delete( { path: `${BASE_ACTION_API_PATH}/action/{id}`, @@ -32,11 +30,7 @@ export const deleteActionRoute = (router: IRouter, licenseState: ILicenseState) params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/execute.ts b/x-pack/plugins/actions/server/routes/execute.ts index 8191b6946d332..b2398a8b366e6 100644 --- a/x-pack/plugins/actions/server/routes/execute.ts +++ b/x-pack/plugins/actions/server/routes/execute.ts @@ -3,17 +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 { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib'; -import { ActionTypeExecutorResult } from '../types'; +import { ActionTypeExecutorResult, ActionsRequestHandlerContext } from '../types'; import { BASE_ACTION_API_PATH } from '../../common'; import { asHttpRequestExecutionSource } from '../lib/action_execution_source'; @@ -25,7 +19,10 @@ const bodySchema = schema.object({ params: schema.recordOf(schema.string(), schema.any()), }); -export const executeActionRoute = (router: IRouter, licenseState: ILicenseState) => { +export const executeActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { router.post( { path: `${BASE_ACTION_API_PATH}/action/{id}/_execute`, @@ -34,11 +31,7 @@ export const executeActionRoute = (router: IRouter, licenseState: ILicenseState) params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, TypeOf>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.actions) { diff --git a/x-pack/plugins/actions/server/routes/get.ts b/x-pack/plugins/actions/server/routes/get.ts index 33577fad87c04..401fa93bebd80 100644 --- a/x-pack/plugins/actions/server/routes/get.ts +++ b/x-pack/plugins/actions/server/routes/get.ts @@ -4,22 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; import { ILicenseState, verifyApiAccess } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; +import { ActionsRequestHandlerContext } from '../types'; const paramSchema = schema.object({ id: schema.string(), }); -export const getActionRoute = (router: IRouter, licenseState: ILicenseState) => { +export const getActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { router.get( { path: `${BASE_ACTION_API_PATH}/action/{id}`, @@ -27,11 +25,7 @@ export const getActionRoute = (router: IRouter, licenseState: ILicenseState) => params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/get_all.ts b/x-pack/plugins/actions/server/routes/get_all.ts index 1b57f31d14a0d..fa3bd20858b0d 100644 --- a/x-pack/plugins/actions/server/routes/get_all.ts +++ b/x-pack/plugins/actions/server/routes/get_all.ts @@ -4,27 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { IRouter } from 'kibana/server'; import { ILicenseState, verifyApiAccess } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; +import { ActionsRequestHandlerContext } from '../types'; -export const getAllActionRoute = (router: IRouter, licenseState: ILicenseState) => { +export const getAllActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { router.get( { path: `${BASE_ACTION_API_PATH}`, validate: {}, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/list_action_types.ts b/x-pack/plugins/actions/server/routes/list_action_types.ts index c960a6bac6de0..b84eede91306e 100644 --- a/x-pack/plugins/actions/server/routes/list_action_types.ts +++ b/x-pack/plugins/actions/server/routes/list_action_types.ts @@ -4,27 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { IRouter } from 'kibana/server'; import { ILicenseState, verifyApiAccess } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; +import { ActionsRequestHandlerContext } from '../types'; -export const listActionTypesRoute = (router: IRouter, licenseState: ILicenseState) => { +export const listActionTypesRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { router.get( { path: `${BASE_ACTION_API_PATH}/list_action_types`, validate: {}, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/routes/update.ts b/x-pack/plugins/actions/server/routes/update.ts index 328ce74ef0b08..215d617d270b8 100644 --- a/x-pack/plugins/actions/server/routes/update.ts +++ b/x-pack/plugins/actions/server/routes/update.ts @@ -4,16 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; +import { ActionsRequestHandlerContext } from '../types'; const paramSchema = schema.object({ id: schema.string(), @@ -25,7 +20,10 @@ const bodySchema = schema.object({ secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), }); -export const updateActionRoute = (router: IRouter, licenseState: ILicenseState) => { +export const updateActionRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { router.put( { path: `${BASE_ACTION_API_PATH}/action/{id}`, @@ -34,11 +32,7 @@ export const updateActionRoute = (router: IRouter, licenseState: ILicenseState) params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, TypeOf>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.actions) { return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 81d6c3550a53c..f545c0fc96633 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -15,6 +15,7 @@ import { SavedObjectsClientContract, SavedObjectAttributes, ElasticsearchClient, + RequestHandlerContext, } from '../../../../src/core/server'; import { ActionTypeExecutorResult } from '../common'; export { ActionTypeExecutorResult } from '../common'; @@ -40,13 +41,13 @@ export interface Services { getLegacyScopedClusterClient(clusterClient: ILegacyClusterClient): ILegacyScopedClusterClient; } -declare module 'src/core/server' { - interface RequestHandlerContext { - actions?: { - getActionsClient: () => ActionsClient; - listTypes: ActionTypeRegistry['list']; - }; - } +export interface ActionsApiRequestHandlerContext { + getActionsClient: () => ActionsClient; + listTypes: ActionTypeRegistry['list']; +} + +export interface ActionsRequestHandlerContext extends RequestHandlerContext { + actions: ActionsApiRequestHandlerContext; } export interface ActionsPlugin { diff --git a/x-pack/plugins/alerts/server/index.ts b/x-pack/plugins/alerts/server/index.ts index da56da671f9b0..50698b840f9c7 100644 --- a/x-pack/plugins/alerts/server/index.ts +++ b/x-pack/plugins/alerts/server/index.ts @@ -12,7 +12,7 @@ import { AlertsConfigType } from './types'; export type AlertsClient = PublicMethodsOf; -export { +export type { ActionVariable, AlertType, ActionGroup, @@ -26,6 +26,7 @@ export { PartialAlert, AlertInstanceState, AlertInstanceContext, + AlertingApiRequestHandlerContext, } from './types'; export { PluginSetupContract, PluginStartContract } from './plugin'; export { FindResult } from './alerts_client'; diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts index cb165fa56d046..fc76b2383b5bd 100644 --- a/x-pack/plugins/alerts/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -28,13 +28,13 @@ import { CoreStart, SavedObjectsServiceStart, IContextProvider, - RequestHandler, ElasticsearchServiceStart, ILegacyClusterClient, StatusServiceSetup, ServiceStatus, SavedObjectsBulkGetObject, } from '../../../../src/core/server'; +import type { AlertingRequestHandlerContext } from './types'; import { aggregateAlertRoute, @@ -255,10 +255,13 @@ export class AlertingPlugin { initializeAlertingHealth(this.logger, plugins.taskManager, core.getStartServices()); - core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext(core)); + core.http.registerRouteHandlerContext( + 'alerting', + this.createRouteHandlerContext(core) + ); // Routes - const router = core.http.createRouter(); + const router = core.http.createRouter(); // Register routes aggregateAlertRoute(router, this.licenseState); createAlertRoute(router, this.licenseState); @@ -392,7 +395,7 @@ export class AlertingPlugin { private createRouteHandlerContext = ( core: CoreSetup - ): IContextProvider, 'alerting'> => { + ): IContextProvider => { const { alertTypeRegistry, alertsClientFactory } = this; return async function alertsRouteHandlerContext(context, request) { const [{ savedObjects }] = await core.getStartServices(); diff --git a/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts index b3f407b20c142..9883d5ec0dd5f 100644 --- a/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts @@ -4,18 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - RequestHandlerContext, - KibanaRequest, - KibanaResponseFactory, - ILegacyClusterClient, -} from 'kibana/server'; +import { KibanaRequest, KibanaResponseFactory, ILegacyClusterClient } from 'kibana/server'; import { identity } from 'lodash'; import type { MethodKeysOf } from '@kbn/utility-types'; import { httpServerMock } from '../../../../../src/core/server/mocks'; import { alertsClientMock, AlertsClientMock } from '../alerts_client.mock'; import { AlertsHealth, AlertType } from '../../common'; import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; +import type { AlertingRequestHandlerContext } from '../types'; export function mockHandlerArguments( { @@ -32,7 +28,11 @@ export function mockHandlerArguments( }, req: unknown, res?: Array> -): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { +): [ + AlertingRequestHandlerContext, + KibanaRequest, + KibanaResponseFactory +] { const listTypes = jest.fn(() => listTypesRes); return [ ({ @@ -44,7 +44,7 @@ export function mockHandlerArguments( }, getFrameworkHealth, }, - } as unknown) as RequestHandlerContext, + } as unknown) as AlertingRequestHandlerContext, req as KibanaRequest, mockResponseFactory(res), ]; diff --git a/x-pack/plugins/alerts/server/routes/aggregate.ts b/x-pack/plugins/alerts/server/routes/aggregate.ts index 0fcfb6f6147e7..3b809196f9b70 100644 --- a/x-pack/plugins/alerts/server/routes/aggregate.ts +++ b/x-pack/plugins/alerts/server/routes/aggregate.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -38,7 +32,7 @@ const querySchema = schema.object({ filter: schema.maybe(schema.string()), }); -export const aggregateAlertRoute = (router: IRouter, licenseState: ILicenseState) => { +export const aggregateAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.get( { path: `${BASE_ALERT_API_PATH}/_aggregate`, @@ -46,11 +40,7 @@ export const aggregateAlertRoute = (router: IRouter, licenseState: ILicenseState query: querySchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/create.ts b/x-pack/plugins/alerts/server/routes/create.ts index a79a9d40b236f..2b6735d9063df 100644 --- a/x-pack/plugins/alerts/server/routes/create.ts +++ b/x-pack/plugins/alerts/server/routes/create.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { validateDurationSchema } from '../lib'; @@ -48,7 +42,7 @@ export const bodySchema = schema.object({ notifyWhen: schema.nullable(schema.string({ validate: validateNotifyWhenType })), }); -export const createAlertRoute = (router: IRouter, licenseState: ILicenseState) => { +export const createAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { path: `${BASE_ALERT_API_PATH}/alert`, @@ -57,11 +51,7 @@ export const createAlertRoute = (router: IRouter, licenseState: ILicenseState) = }, }, handleDisabledApiKeysError( - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { diff --git a/x-pack/plugins/alerts/server/routes/delete.ts b/x-pack/plugins/alerts/server/routes/delete.ts index 3ac975d3a1546..12991e28d67ce 100644 --- a/x-pack/plugins/alerts/server/routes/delete.ts +++ b/x-pack/plugins/alerts/server/routes/delete.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -20,7 +14,7 @@ const paramSchema = schema.object({ id: schema.string(), }); -export const deleteAlertRoute = (router: IRouter, licenseState: ILicenseState) => { +export const deleteAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.delete( { path: `${BASE_ALERT_API_PATH}/alert/{id}`, @@ -28,11 +22,7 @@ export const deleteAlertRoute = (router: IRouter, licenseState: ILicenseState) = params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/disable.ts b/x-pack/plugins/alerts/server/routes/disable.ts index e96cb397f554b..91663bd852879 100644 --- a/x-pack/plugins/alerts/server/routes/disable.ts +++ b/x-pack/plugins/alerts/server/routes/disable.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -21,7 +15,7 @@ const paramSchema = schema.object({ id: schema.string(), }); -export const disableAlertRoute = (router: IRouter, licenseState: ILicenseState) => { +export const disableAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { path: `${BASE_ALERT_API_PATH}/alert/{id}/_disable`, @@ -29,11 +23,7 @@ export const disableAlertRoute = (router: IRouter, licenseState: ILicenseState) params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/enable.ts b/x-pack/plugins/alerts/server/routes/enable.ts index 81c5027c7587b..cbfcbc44d9f5f 100644 --- a/x-pack/plugins/alerts/server/routes/enable.ts +++ b/x-pack/plugins/alerts/server/routes/enable.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -22,7 +16,7 @@ const paramSchema = schema.object({ id: schema.string(), }); -export const enableAlertRoute = (router: IRouter, licenseState: ILicenseState) => { +export const enableAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { path: `${BASE_ALERT_API_PATH}/alert/{id}/_enable`, @@ -31,11 +25,7 @@ export const enableAlertRoute = (router: IRouter, licenseState: ILicenseState) = }, }, handleDisabledApiKeysError( - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/find.ts b/x-pack/plugins/alerts/server/routes/find.ts index 487ff571187f4..195abf0a15a9f 100644 --- a/x-pack/plugins/alerts/server/routes/find.ts +++ b/x-pack/plugins/alerts/server/routes/find.ts @@ -4,14 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; + import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -43,7 +38,7 @@ const querySchema = schema.object({ filter: schema.maybe(schema.string()), }); -export const findAlertRoute = (router: IRouter, licenseState: ILicenseState) => { +export const findAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.get( { path: `${BASE_ALERT_API_PATH}/_find`, @@ -51,11 +46,7 @@ export const findAlertRoute = (router: IRouter, licenseState: ILicenseState) => query: querySchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/get.ts b/x-pack/plugins/alerts/server/routes/get.ts index ae592f37cd55c..ddf16d1022c89 100644 --- a/x-pack/plugins/alerts/server/routes/get.ts +++ b/x-pack/plugins/alerts/server/routes/get.ts @@ -4,23 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; +import type { AlertingRouter } from '../types'; const paramSchema = schema.object({ id: schema.string(), }); -export const getAlertRoute = (router: IRouter, licenseState: ILicenseState) => { +export const getAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.get( { path: `${BASE_ALERT_API_PATH}/alert/{id}`, @@ -28,11 +22,7 @@ export const getAlertRoute = (router: IRouter, licenseState: ILicenseState) => { params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/get_alert_instance_summary.ts b/x-pack/plugins/alerts/server/routes/get_alert_instance_summary.ts index 33f331f7dce02..095b8e492467c 100644 --- a/x-pack/plugins/alerts/server/routes/get_alert_instance_summary.ts +++ b/x-pack/plugins/alerts/server/routes/get_alert_instance_summary.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -24,7 +18,10 @@ const querySchema = schema.object({ dateStart: schema.maybe(schema.string()), }); -export const getAlertInstanceSummaryRoute = (router: IRouter, licenseState: ILicenseState) => { +export const getAlertInstanceSummaryRoute = ( + router: AlertingRouter, + licenseState: ILicenseState +) => { router.get( { path: `${BASE_ALERT_API_PATH}/alert/{id}/_instance_summary`, @@ -33,11 +30,7 @@ export const getAlertInstanceSummaryRoute = (router: IRouter, licenseState: ILic query: querySchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, TypeOf, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/get_alert_state.ts b/x-pack/plugins/alerts/server/routes/get_alert_state.ts index 52ad8f9f31874..e818e5d04304d 100644 --- a/x-pack/plugins/alerts/server/routes/get_alert_state.ts +++ b/x-pack/plugins/alerts/server/routes/get_alert_state.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -20,7 +14,7 @@ const paramSchema = schema.object({ id: schema.string(), }); -export const getAlertStateRoute = (router: IRouter, licenseState: ILicenseState) => { +export const getAlertStateRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.get( { path: `${BASE_ALERT_API_PATH}/alert/{id}/state`, @@ -28,11 +22,7 @@ export const getAlertStateRoute = (router: IRouter, licenseState: ILicenseState) params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/health.ts b/x-pack/plugins/alerts/server/routes/health.ts index 962ad7e1bb29a..b21f389e3b088 100644 --- a/x-pack/plugins/alerts/server/routes/health.ts +++ b/x-pack/plugins/alerts/server/routes/health.ts @@ -4,13 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { AlertingFrameworkHealth } from '../types'; @@ -28,7 +22,7 @@ interface XPackUsageSecurity { } export function healthRoute( - router: IRouter, + router: AlertingRouter, licenseState: ILicenseState, encryptedSavedObjects: EncryptedSavedObjectsPluginSetup ) { @@ -37,11 +31,7 @@ export function healthRoute( path: '/api/alerts/_health', validate: false, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/lib/error_handler.ts b/x-pack/plugins/alerts/server/routes/lib/error_handler.ts index e0c620b0670c9..f0ee9087cbe8f 100644 --- a/x-pack/plugins/alerts/server/routes/lib/error_handler.ts +++ b/x-pack/plugins/alerts/server/routes/lib/error_handler.ts @@ -5,22 +5,10 @@ */ import { i18n } from '@kbn/i18n'; -import { - RequestHandler, - KibanaRequest, - KibanaResponseFactory, - RequestHandlerContext, - RouteMethod, -} from 'kibana/server'; +import { RequestHandlerWrapper } from 'kibana/server'; -export function handleDisabledApiKeysError( - handler: RequestHandler -): RequestHandler { - return async ( - context: RequestHandlerContext, - request: KibanaRequest, - response: KibanaResponseFactory - ) => { +export const handleDisabledApiKeysError: RequestHandlerWrapper = (handler) => { + return async (context, request, response) => { try { return await handler(context, request, response); } catch (e) { @@ -36,7 +24,7 @@ export function handleDisabledApiKeysError( throw e; } }; -} +}; export function isApiKeyDisabledError(e: Error) { return e?.message?.includes('api keys are not enabled') ?? false; diff --git a/x-pack/plugins/alerts/server/routes/list_alert_types.ts b/x-pack/plugins/alerts/server/routes/list_alert_types.ts index 9b4b352e211f1..b5cefccfd4b0d 100644 --- a/x-pack/plugins/alerts/server/routes/list_alert_types.ts +++ b/x-pack/plugins/alerts/server/routes/list_alert_types.ts @@ -4,28 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; -export const listAlertTypesRoute = (router: IRouter, licenseState: ILicenseState) => { +export const listAlertTypesRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.get( { path: `${BASE_ALERT_API_PATH}/list_alert_types`, validate: {}, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/mute_all.ts b/x-pack/plugins/alerts/server/routes/mute_all.ts index 224216961bb7f..92127a7cec676 100644 --- a/x-pack/plugins/alerts/server/routes/mute_all.ts +++ b/x-pack/plugins/alerts/server/routes/mute_all.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -21,7 +15,7 @@ const paramSchema = schema.object({ id: schema.string(), }); -export const muteAllAlertRoute = (router: IRouter, licenseState: ILicenseState) => { +export const muteAllAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { path: `${BASE_ALERT_API_PATH}/alert/{id}/_mute_all`, @@ -29,11 +23,7 @@ export const muteAllAlertRoute = (router: IRouter, licenseState: ILicenseState) params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/mute_instance.ts b/x-pack/plugins/alerts/server/routes/mute_instance.ts index b374866177231..923bce5b8c316 100644 --- a/x-pack/plugins/alerts/server/routes/mute_instance.ts +++ b/x-pack/plugins/alerts/server/routes/mute_instance.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -24,7 +18,7 @@ const paramSchema = schema.object({ alert_instance_id: schema.string(), }); -export const muteAlertInstanceRoute = (router: IRouter, licenseState: ILicenseState) => { +export const muteAlertInstanceRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { path: `${BASE_ALERT_API_PATH}/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`, @@ -32,11 +26,7 @@ export const muteAlertInstanceRoute = (router: IRouter, licenseState: ILicenseSt params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/unmute_all.ts b/x-pack/plugins/alerts/server/routes/unmute_all.ts index e249ec7ffa58f..8709cf3ae887e 100644 --- a/x-pack/plugins/alerts/server/routes/unmute_all.ts +++ b/x-pack/plugins/alerts/server/routes/unmute_all.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -21,7 +15,7 @@ const paramSchema = schema.object({ id: schema.string(), }); -export const unmuteAllAlertRoute = (router: IRouter, licenseState: ILicenseState) => { +export const unmuteAllAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { path: `${BASE_ALERT_API_PATH}/alert/{id}/_unmute_all`, @@ -29,11 +23,7 @@ export const unmuteAllAlertRoute = (router: IRouter, licenseState: ILicenseState params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/unmute_instance.ts b/x-pack/plugins/alerts/server/routes/unmute_instance.ts index bcab6e21578aa..d1c62ce365920 100644 --- a/x-pack/plugins/alerts/server/routes/unmute_instance.ts +++ b/x-pack/plugins/alerts/server/routes/unmute_instance.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -22,7 +16,7 @@ const paramSchema = schema.object({ alertInstanceId: schema.string(), }); -export const unmuteAlertInstanceRoute = (router: IRouter, licenseState: ILicenseState) => { +export const unmuteAlertInstanceRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { path: `${BASE_ALERT_API_PATH}/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`, @@ -30,11 +24,7 @@ export const unmuteAlertInstanceRoute = (router: IRouter, licenseState: ILicense params: paramSchema, }, }, - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/update.ts b/x-pack/plugins/alerts/server/routes/update.ts index d3ecc9eb3e381..2e70843dcb142 100644 --- a/x-pack/plugins/alerts/server/routes/update.ts +++ b/x-pack/plugins/alerts/server/routes/update.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { validateDurationSchema } from '../lib'; @@ -43,7 +37,7 @@ const bodySchema = schema.object({ notifyWhen: schema.nullable(schema.string({ validate: validateNotifyWhenType })), }); -export const updateAlertRoute = (router: IRouter, licenseState: ILicenseState) => { +export const updateAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.put( { path: `${BASE_ALERT_API_PATH}/alert/{id}`, @@ -53,11 +47,7 @@ export const updateAlertRoute = (router: IRouter, licenseState: ILicenseState) = }, }, handleDisabledApiKeysError( - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, TypeOf>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/routes/update_api_key.ts b/x-pack/plugins/alerts/server/routes/update_api_key.ts index fb7639d975980..743435c96e6ab 100644 --- a/x-pack/plugins/alerts/server/routes/update_api_key.ts +++ b/x-pack/plugins/alerts/server/routes/update_api_key.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import type { AlertingRouter } from '../types'; import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; @@ -22,7 +16,7 @@ const paramSchema = schema.object({ id: schema.string(), }); -export const updateApiKeyRoute = (router: IRouter, licenseState: ILicenseState) => { +export const updateApiKeyRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { path: `${BASE_ALERT_API_PATH}/alert/{id}/_update_api_key`, @@ -31,11 +25,7 @@ export const updateApiKeyRoute = (router: IRouter, licenseState: ILicenseState) }, }, handleDisabledApiKeysError( - router.handleLegacyErrors(async function ( - context: RequestHandlerContext, - req: KibanaRequest, unknown, unknown>, - res: KibanaResponseFactory - ): Promise { + router.handleLegacyErrors(async function (context, req, res) { verifyApiAccess(licenseState); if (!context.alerting) { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 39c52d9653aaa..0e686bc1f21d7 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -3,6 +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 type { IRouter, RequestHandlerContext } from 'src/core/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { PublicAlertInstance } from './alert_instance'; import { AlertTypeRegistry as OrigAlertTypeRegistry } from './alert_type_registry'; @@ -37,16 +38,27 @@ export type WithoutQueryAndParams = Pick Services; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; -declare module 'src/core/server' { - interface RequestHandlerContext { - alerting?: { - getAlertsClient: () => AlertsClient; - listTypes: AlertTypeRegistry['list']; - getFrameworkHealth: () => Promise; - }; - } +/** + * @public + */ +export interface AlertingApiRequestHandlerContext { + getAlertsClient: () => AlertsClient; + listTypes: AlertTypeRegistry['list']; + getFrameworkHealth: () => Promise; +} + +/** + * @internal + */ +export interface AlertingRequestHandlerContext extends RequestHandlerContext { + alerting: AlertingApiRequestHandlerContext; } +/** + * @internal + */ +export type AlertingRouter = IRouter; + export interface Services { /** * @deprecated Use `scopedClusterClient` instead. diff --git a/x-pack/plugins/apm/server/feature.ts b/x-pack/plugins/apm/server/feature.ts index 9eba18d44ad50..deb89314fc626 100644 --- a/x-pack/plugins/apm/server/feature.ts +++ b/x-pack/plugins/apm/server/feature.ts @@ -10,7 +10,7 @@ import { AlertType } from '../common/alert_types'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { LicensingPluginSetup, - LicensingRequestHandlerContext, + LicensingApiRequestHandlerContext, } from '../../licensing/server'; export const APM_FEATURE = { @@ -97,7 +97,7 @@ export function notifyFeatureUsage({ licensingPlugin, featureName, }: { - licensingPlugin: LicensingRequestHandlerContext; + licensingPlugin: LicensingApiRequestHandlerContext; featureName: FeatureName; }) { const feature = features[featureName]; diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 44269b1775953..09b75137e12df 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -14,7 +14,6 @@ import { Logger, Plugin, PluginInitializerContext, - RequestHandlerContext, } from 'src/core/server'; import { APMConfig, APMXPackConfig, mergeConfigs } from '.'; import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server'; @@ -42,6 +41,7 @@ import { createApmApi } from './routes/create_apm_api'; import { apmIndices, apmTelemetry } from './saved_objects'; import { createElasticCloudInstructions } from './tutorial/elastic_cloud'; import { uiSettings } from './ui_settings'; +import type { ApmPluginRequestHandlerContext } from './routes/typings'; export interface APMPluginSetup { config$: Observable; @@ -49,7 +49,7 @@ export interface APMPluginSetup { createApmEventClient: (params: { debug?: boolean; request: KibanaRequest; - context: RequestHandlerContext; + context: ApmPluginRequestHandlerContext; }) => Promise>; } @@ -166,7 +166,7 @@ export class APMPlugin implements Plugin { }: { debug?: boolean; request: KibanaRequest; - context: RequestHandlerContext; + context: ApmPluginRequestHandlerContext; }) => { const [indices, includeFrozen] = await Promise.all([ boundGetApmIndices(), diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts index 94711cf76c145..cfb31670bd521 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.ts @@ -15,6 +15,7 @@ import { strictKeysRt } from '../../../common/runtime_types/strict_keys_rt'; import { APMConfig } from '../..'; import { ServerAPI } from '../typings'; import { jsonRt } from '../../../common/runtime_types/json_rt'; +import type { ApmPluginRequestHandlerContext } from '../typings'; const debugRt = t.exact( t.partial({ @@ -73,7 +74,10 @@ export function createApi() { const anyObject = schema.object({}, { unknowns: 'allow' }); - (router[typedRouterMethod] as RouteRegistrar)( + (router[typedRouterMethod] as RouteRegistrar< + typeof typedRouterMethod, + ApmPluginRequestHandlerContext + >)( { path, options, diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts index 81b25e572a28d..7d7a5c3b0dab3 100644 --- a/x-pack/plugins/apm/server/routes/typings.ts +++ b/x-pack/plugins/apm/server/routes/typings.ts @@ -14,6 +14,7 @@ import { import { Observable } from 'rxjs'; import { RequiredKeys } from 'utility-types'; import { ObservabilityPluginSetup } from '../../../observability/server'; +import { LicensingApiRequestHandlerContext } from '../../../licensing/server'; import { SecurityPluginSetup } from '../../../security/server'; import { MlPluginSetup } from '../../../ml/server'; import { FetchOptions } from '../../common/fetch_options'; @@ -64,9 +65,16 @@ export interface Route< handler: RouteHandler; } +/** + * @internal + */ +export interface ApmPluginRequestHandlerContext extends RequestHandlerContext { + licensing: LicensingApiRequestHandlerContext; +} + export type APMRequestHandlerContext< TRouteParams = {} -> = RequestHandlerContext & { +> = ApmPluginRequestHandlerContext & { params: TRouteParams & { query: { _debug: boolean } }; config: APMConfig; logger: Logger; diff --git a/x-pack/plugins/beats_management/server/lib/types.ts b/x-pack/plugins/beats_management/server/lib/types.ts index d86aa8652fdbc..20467af7018f7 100644 --- a/x-pack/plugins/beats_management/server/lib/types.ts +++ b/x-pack/plugins/beats_management/server/lib/types.ts @@ -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 type { IRouter, RequestHandlerContext } from 'src/core/server'; import { DatabaseAdapter } from './adapters/database/adapter_types'; import { FrameworkUser } from './adapters/framework/adapter_types'; import { BeatEventsLib } from './beat_events'; @@ -40,3 +40,20 @@ export interface AsyncResponse { export interface AsyncResponse { data: DataType; } + +/** + * @internal + */ +export type BeatsManagementApiRequestHandlerContext = CMServerLibs; + +/** + * @internal + */ +export interface BeatsManagementRequestHandlerContext extends RequestHandlerContext { + beatsManagement: CMServerLibs; +} + +/** + * @internal + */ +export type BeatsManagementRouter = IRouter; diff --git a/x-pack/plugins/beats_management/server/plugin.ts b/x-pack/plugins/beats_management/server/plugin.ts index fde0a2efecdda..d52de39ed458f 100644 --- a/x-pack/plugins/beats_management/server/plugin.ts +++ b/x-pack/plugins/beats_management/server/plugin.ts @@ -5,17 +5,12 @@ */ import { take } from 'rxjs/operators'; -import { - CoreSetup, - CoreStart, - Plugin, - PluginInitializerContext, -} from '../../../../src/core/server'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { SecurityPluginSetup } from '../../security/server'; import { LicensingPluginStart } from '../../licensing/server'; import { BeatsManagementConfigType } from '../common'; -import { CMServerLibs } from './lib/types'; +import type { BeatsManagementRequestHandlerContext, CMServerLibs } from './lib/types'; import { registerRoutes } from './routes'; import { compose } from './lib/compose/kibana'; import { INDEX_NAMES } from '../common/constants'; @@ -30,12 +25,6 @@ interface StartDeps { licensing: LicensingPluginStart; } -declare module 'src/core/server' { - interface RequestHandlerContext { - beatsManagement?: CMServerLibs; - } -} - export class BeatsManagementPlugin implements Plugin<{}, {}, SetupDeps, StartDeps> { private securitySetup?: SecurityPluginSetup; private beatsLibs?: CMServerLibs; @@ -47,12 +36,15 @@ export class BeatsManagementPlugin implements Plugin<{}, {}, SetupDeps, StartDep public async setup(core: CoreSetup, { features, security }: SetupDeps) { this.securitySetup = security; - const router = core.http.createRouter(); + const router = core.http.createRouter(); registerRoutes(router); - core.http.registerRouteHandlerContext('beatsManagement', (_, req) => { - return this.beatsLibs!; - }); + core.http.registerRouteHandlerContext( + 'beatsManagement', + (_, req) => { + return this.beatsLibs!; + } + ); features.registerElasticsearchFeature({ id: 'beats_management', diff --git a/x-pack/plugins/beats_management/server/routes/beats/configuration.ts b/x-pack/plugins/beats_management/server/routes/beats/configuration.ts index 1496e4bbfc99f..363b8e3f07163 100644 --- a/x-pack/plugins/beats_management/server/routes/beats/configuration.ts +++ b/x-pack/plugins/beats_management/server/routes/beats/configuration.ts @@ -5,12 +5,12 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { ConfigurationBlock } from '../../../common/domain_types'; import { ReturnTypeList } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerGetBeatConfigurationRoute = (router: IRouter) => { +export const registerGetBeatConfigurationRoute = (router: BeatsManagementRouter) => { router.get( { path: '/api/beats/agent/{beatId}/configuration', diff --git a/x-pack/plugins/beats_management/server/routes/beats/enroll.ts b/x-pack/plugins/beats_management/server/routes/beats/enroll.ts index be8fff3b7c437..919d52942cd62 100644 --- a/x-pack/plugins/beats_management/server/routes/beats/enroll.ts +++ b/x-pack/plugins/beats_management/server/routes/beats/enroll.ts @@ -5,14 +5,14 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { BeatsManagementRouter } from '../../lib/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ensureRawRequest } from '../../../../../../src/core/server/http/router'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { BeatEnrollmentStatus } from '../../lib/types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerBeatEnrollmentRoute = (router: IRouter) => { +export const registerBeatEnrollmentRoute = (router: BeatsManagementRouter) => { // TODO: write to Kibana audit log file https://github.com/elastic/kibana/issues/26024 router.post( { diff --git a/x-pack/plugins/beats_management/server/routes/beats/events.ts b/x-pack/plugins/beats_management/server/routes/beats/events.ts index b87e6d684228a..c8e66c6d745d0 100644 --- a/x-pack/plugins/beats_management/server/routes/beats/events.ts +++ b/x-pack/plugins/beats_management/server/routes/beats/events.ts @@ -5,11 +5,11 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'kibana/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { ReturnTypeBulkAction } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerBeatEventsRoute = (router: IRouter) => { +export const registerBeatEventsRoute = (router: BeatsManagementRouter) => { router.post( { path: '/api/beats/{beatId}/events', diff --git a/x-pack/plugins/beats_management/server/routes/beats/get.ts b/x-pack/plugins/beats_management/server/routes/beats/get.ts index 8762f325e7484..8e2279c7b9c21 100644 --- a/x-pack/plugins/beats_management/server/routes/beats/get.ts +++ b/x-pack/plugins/beats_management/server/routes/beats/get.ts @@ -5,12 +5,12 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'kibana/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { CMBeat } from '../../../common/domain_types'; import { ReturnTypeGet } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerGetBeatRoute = (router: IRouter) => { +export const registerGetBeatRoute = (router: BeatsManagementRouter) => { router.get( { path: '/api/beats/agent/{beatId}/{token?}', diff --git a/x-pack/plugins/beats_management/server/routes/beats/list.ts b/x-pack/plugins/beats_management/server/routes/beats/list.ts index e4108238e3f2f..1ae77134f2b22 100644 --- a/x-pack/plugins/beats_management/server/routes/beats/list.ts +++ b/x-pack/plugins/beats_management/server/routes/beats/list.ts @@ -5,13 +5,13 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'kibana/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { CMBeat } from '../../../common/domain_types'; import { ReturnTypeList } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerListAgentsRoute = (router: IRouter) => { +export const registerListAgentsRoute = (router: BeatsManagementRouter) => { router.get( { path: '/api/beats/agents/{listByAndValue*}', @@ -33,7 +33,7 @@ export const registerListAgentsRoute = (router: IRouter) => { requiredLicense: REQUIRED_LICENSES, }, async (context, request, response) => { - const beatsManagement = context.beatsManagement!; + const beatsManagement = context.beatsManagement; const user = beatsManagement.framework.getUser(request); const listByAndValueParts = request.params.listByAndValue?.split('/') ?? []; diff --git a/x-pack/plugins/beats_management/server/routes/beats/tag_assignment.ts b/x-pack/plugins/beats_management/server/routes/beats/tag_assignment.ts index 0397f8ec4398e..12205747ed566 100644 --- a/x-pack/plugins/beats_management/server/routes/beats/tag_assignment.ts +++ b/x-pack/plugins/beats_management/server/routes/beats/tag_assignment.ts @@ -5,14 +5,14 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'kibana/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { ReturnTypeBulkAction } from '../../../common/return_types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import type { BeatsTagAssignment } from '../../../public/lib/adapters/beats/adapter_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerTagAssignmentsRoute = (router: IRouter) => { +export const registerTagAssignmentsRoute = (router: BeatsManagementRouter) => { // TODO: write to Kibana audit log file https://github.com/elastic/kibana/issues/26024 router.post( { diff --git a/x-pack/plugins/beats_management/server/routes/beats/tag_removal.ts b/x-pack/plugins/beats_management/server/routes/beats/tag_removal.ts index a04ed81fb183b..195810d6bf3e4 100644 --- a/x-pack/plugins/beats_management/server/routes/beats/tag_removal.ts +++ b/x-pack/plugins/beats_management/server/routes/beats/tag_removal.ts @@ -5,12 +5,12 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'kibana/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { ReturnTypeBulkAction } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerTagRemovalsRoute = (router: IRouter) => { +export const registerTagRemovalsRoute = (router: BeatsManagementRouter) => { // TODO: write to Kibana audit log file https://github.com/elastic/kibana/issues/26024 router.post( { @@ -33,7 +33,7 @@ export const registerTagRemovalsRoute = (router: IRouter) => { requiredRoles: ['beats_admin'], }, async (context, request, response) => { - const beatsManagement = context.beatsManagement!; + const beatsManagement = context.beatsManagement; const user = beatsManagement.framework.getUser(request); const { removals } = request.body; diff --git a/x-pack/plugins/beats_management/server/routes/beats/update.ts b/x-pack/plugins/beats_management/server/routes/beats/update.ts index 21bd6555b28dd..1e9c2db025578 100644 --- a/x-pack/plugins/beats_management/server/routes/beats/update.ts +++ b/x-pack/plugins/beats_management/server/routes/beats/update.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'kibana/server'; +import type { BeatsManagementRouter } from '../../lib/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ensureRawRequest } from '../../../../../../src/core/server/http/router'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; @@ -14,7 +14,7 @@ import { ReturnTypeUpdate } from '../../../common/return_types'; import { internalUser } from '../../lib/adapters/framework/adapter_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerBeatUpdateRoute = (router: IRouter) => { +export const registerBeatUpdateRoute = (router: BeatsManagementRouter) => { // TODO: write to Kibana audit log file (include who did the verification as well) https://github.com/elastic/kibana/issues/26024 router.put( { @@ -44,7 +44,7 @@ export const registerBeatUpdateRoute = (router: IRouter) => { requiredRoles: ['beats_admin'], }, async (context, request, response) => { - const beatsManagement = context.beatsManagement!; + const beatsManagement = context.beatsManagement; const accessToken = request.headers['kbn-beats-access-token'] as string; const { beatId } = request.params; const user = beatsManagement.framework.getUser(request); diff --git a/x-pack/plugins/beats_management/server/routes/configurations/delete.ts b/x-pack/plugins/beats_management/server/routes/configurations/delete.ts index b60d3bd2d5a94..4fee192c93626 100644 --- a/x-pack/plugins/beats_management/server/routes/configurations/delete.ts +++ b/x-pack/plugins/beats_management/server/routes/configurations/delete.ts @@ -5,12 +5,12 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { ReturnTypeBulkDelete } from '../../../common/return_types'; -export const registerDeleteConfigurationBlocksRoute = (router: IRouter) => { +export const registerDeleteConfigurationBlocksRoute = (router: BeatsManagementRouter) => { router.delete( { path: '/api/beats/configurations/{ids}', @@ -26,7 +26,7 @@ export const registerDeleteConfigurationBlocksRoute = (router: IRouter) => { requiredRoles: ['beats_admin'], }, async (context, request, response) => { - const beatsManagement = context.beatsManagement!; + const beatsManagement = context.beatsManagement; const ids = request.params.ids.split(',').filter((id) => id.length > 0); const user = beatsManagement.framework.getUser(request); diff --git a/x-pack/plugins/beats_management/server/routes/configurations/get.ts b/x-pack/plugins/beats_management/server/routes/configurations/get.ts index 6f422ca9ca8bd..def913e0204b5 100644 --- a/x-pack/plugins/beats_management/server/routes/configurations/get.ts +++ b/x-pack/plugins/beats_management/server/routes/configurations/get.ts @@ -5,13 +5,13 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { ConfigurationBlock } from '../../../common/domain_types'; import { ReturnTypeList } from '../../../common/return_types'; -export const registerGetConfigurationBlocksRoute = (router: IRouter) => { +export const registerGetConfigurationBlocksRoute = (router: BeatsManagementRouter) => { router.get( { path: '/api/beats/configurations/{tagIds}/{page?}', @@ -28,7 +28,7 @@ export const registerGetConfigurationBlocksRoute = (router: IRouter) => { requiredRoles: ['beats_admin'], }, async (context, request, response) => { - const beatsManagement = context.beatsManagement!; + const beatsManagement = context.beatsManagement; const tagIds = request.params.tagIds.split(',').filter((id) => id.length > 0); const user = beatsManagement.framework.getUser(request); const result = await beatsManagement.configurationBlocks.getForTags( diff --git a/x-pack/plugins/beats_management/server/routes/configurations/upsert.ts b/x-pack/plugins/beats_management/server/routes/configurations/upsert.ts index e235b172e7d0b..002e981875324 100644 --- a/x-pack/plugins/beats_management/server/routes/configurations/upsert.ts +++ b/x-pack/plugins/beats_management/server/routes/configurations/upsert.ts @@ -7,7 +7,7 @@ import { PathReporter } from 'io-ts/lib/PathReporter'; import { isLeft } from 'fp-ts/lib/Either'; import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { REQUIRED_LICENSES } from '../../../common/constants'; import { ConfigurationBlock, @@ -16,7 +16,7 @@ import { import { ReturnTypeBulkUpsert } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerUpsertConfigurationBlocksRoute = (router: IRouter) => { +export const registerUpsertConfigurationBlocksRoute = (router: BeatsManagementRouter) => { // TODO: write to Kibana audit log file router.put( { @@ -31,7 +31,7 @@ export const registerUpsertConfigurationBlocksRoute = (router: IRouter) => { requiredRoles: ['beats_admin'], }, async (context, request, response) => { - const beatsManagement = context.beatsManagement!; + const beatsManagement = context.beatsManagement; const user = beatsManagement.framework.getUser(request); const input = request.body as ConfigurationBlock[]; diff --git a/x-pack/plugins/beats_management/server/routes/index.ts b/x-pack/plugins/beats_management/server/routes/index.ts index 423ecc85a5798..14af4e274b5ae 100644 --- a/x-pack/plugins/beats_management/server/routes/index.ts +++ b/x-pack/plugins/beats_management/server/routes/index.ts @@ -3,8 +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 { IRouter } from 'src/core/server'; +import type { BeatsManagementRouter } from '../lib/types'; import { registerDeleteConfigurationBlocksRoute, registerGetConfigurationBlocksRoute, @@ -29,7 +28,7 @@ import { registerGetBeatConfigurationRoute, } from './beats'; -export const registerRoutes = (router: IRouter) => { +export const registerRoutes = (router: BeatsManagementRouter) => { // configurations registerGetConfigurationBlocksRoute(router); registerDeleteConfigurationBlocksRoute(router); diff --git a/x-pack/plugins/beats_management/server/routes/tags/assignable.ts b/x-pack/plugins/beats_management/server/routes/tags/assignable.ts index 60d4748bf1fa6..254d1015221b5 100644 --- a/x-pack/plugins/beats_management/server/routes/tags/assignable.ts +++ b/x-pack/plugins/beats_management/server/routes/tags/assignable.ts @@ -5,14 +5,14 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; import { flatten } from 'lodash'; +import type { BeatsManagementRouter } from '../../lib/types'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { BeatTag } from '../../../common/domain_types'; import { ReturnTypeBulkGet } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerAssignableTagsRoute = (router: IRouter) => { +export const registerAssignableTagsRoute = (router: BeatsManagementRouter) => { router.get( { path: '/api/beats/tags/assignable/{beatIds}', diff --git a/x-pack/plugins/beats_management/server/routes/tags/delete.ts b/x-pack/plugins/beats_management/server/routes/tags/delete.ts index 78d0c80d42060..4d689dfe49c58 100644 --- a/x-pack/plugins/beats_management/server/routes/tags/delete.ts +++ b/x-pack/plugins/beats_management/server/routes/tags/delete.ts @@ -5,12 +5,12 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'kibana/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { ReturnTypeBulkDelete } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerDeleteTagsWithIdsRoute = (router: IRouter) => { +export const registerDeleteTagsWithIdsRoute = (router: BeatsManagementRouter) => { router.delete( { path: '/api/beats/tags/{tagIds}', diff --git a/x-pack/plugins/beats_management/server/routes/tags/get.ts b/x-pack/plugins/beats_management/server/routes/tags/get.ts index 48da829aa09e5..a4154eaf092a4 100644 --- a/x-pack/plugins/beats_management/server/routes/tags/get.ts +++ b/x-pack/plugins/beats_management/server/routes/tags/get.ts @@ -5,13 +5,13 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { BeatTag } from '../../../common/domain_types'; import { ReturnTypeBulkGet } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerGetTagsWithIdsRoute = (router: IRouter) => { +export const registerGetTagsWithIdsRoute = (router: BeatsManagementRouter) => { router.get( { path: '/api/beats/tags/{tagIds}', diff --git a/x-pack/plugins/beats_management/server/routes/tags/list.ts b/x-pack/plugins/beats_management/server/routes/tags/list.ts index ce913cda337c5..3faa3d0f6662c 100644 --- a/x-pack/plugins/beats_management/server/routes/tags/list.ts +++ b/x-pack/plugins/beats_management/server/routes/tags/list.ts @@ -5,13 +5,13 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'kibana/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { BeatTag } from '../../../common/domain_types'; import { ReturnTypeList } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerListTagsRoute = (router: IRouter) => { +export const registerListTagsRoute = (router: BeatsManagementRouter) => { router.get( { path: '/api/beats/tags', diff --git a/x-pack/plugins/beats_management/server/routes/tags/set.ts b/x-pack/plugins/beats_management/server/routes/tags/set.ts index ef9e181514a55..b80faa5c5c5ef 100644 --- a/x-pack/plugins/beats_management/server/routes/tags/set.ts +++ b/x-pack/plugins/beats_management/server/routes/tags/set.ts @@ -5,13 +5,13 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { REQUIRED_LICENSES } from '../../../common/constants'; import { BeatTag } from '../../../common/domain_types'; import { ReturnTypeUpsert } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; -export const registerSetTagRoute = (router: IRouter) => { +export const registerSetTagRoute = (router: BeatsManagementRouter) => { // TODO: write to Kibana audit log file router.put( { diff --git a/x-pack/plugins/beats_management/server/routes/tokens/create.ts b/x-pack/plugins/beats_management/server/routes/tokens/create.ts index 2fd7d4614c570..5a2f04e21fcc1 100644 --- a/x-pack/plugins/beats_management/server/routes/tokens/create.ts +++ b/x-pack/plugins/beats_management/server/routes/tokens/create.ts @@ -5,14 +5,14 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { BeatsManagementRouter } from '../../lib/types'; import { REQUIRED_LICENSES } from '../../../common/constants/security'; import { ReturnTypeBulkCreate } from '../../../common/return_types'; import { wrapRouteWithSecurity } from '../wrap_route_with_security'; const DEFAULT_NUM_TOKENS = 1; -export const registerCreateTokenRoute = (router: IRouter) => { +export const registerCreateTokenRoute = (router: BeatsManagementRouter) => { // TODO: write to Kibana audit log file router.post( { diff --git a/x-pack/plugins/beats_management/server/routes/wrap_route_with_security.ts b/x-pack/plugins/beats_management/server/routes/wrap_route_with_security.ts index ad4f8080127b2..ab116d2af3b2c 100644 --- a/x-pack/plugins/beats_management/server/routes/wrap_route_with_security.ts +++ b/x-pack/plugins/beats_management/server/routes/wrap_route_with_security.ts @@ -4,24 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - KibanaRequest, - KibanaResponseFactory, - RequestHandler, - RequestHandlerContext, - RouteMethod, -} from 'src/core/server'; +import { KibanaRequest, KibanaResponseFactory, RequestHandler, RouteMethod } from 'src/core/server'; import { difference } from 'lodash'; +import type { BeatsManagementRequestHandlerContext } from '../lib/types'; -export function wrapRouteWithSecurity( +export function wrapRouteWithSecurity< + P, + Q, + B, + Context extends BeatsManagementRequestHandlerContext +>( { requiredLicense = [], requiredRoles = [], }: { requiredLicense?: string[]; requiredRoles?: string[] }, - handler: RequestHandler -): RequestHandler { + handler: RequestHandler +): RequestHandler { return async ( - context: RequestHandlerContext, + context: Context, request: KibanaRequest, response: KibanaResponseFactory ) => { diff --git a/x-pack/plugins/case/server/client/index.test.ts b/x-pack/plugins/case/server/client/index.test.ts index 0c54db11287d8..a7f093f42357c 100644 --- a/x-pack/plugins/case/server/client/index.test.ts +++ b/x-pack/plugins/case/server/client/index.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, RequestHandlerContext } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { createCaseClient } from '.'; import { @@ -19,6 +19,7 @@ import { create } from './cases/create'; import { update } from './cases/update'; import { addComment } from './comments/add'; import { updateAlertsStatus } from './alerts/update_status'; +import type { CasesRequestHandlerContext } from '../types'; jest.mock('./cases/create'); jest.mock('./cases/update'); @@ -32,7 +33,7 @@ const connectorMappingsService = connectorMappingsServiceMock(); const request = {} as KibanaRequest; const savedObjectsClient = savedObjectsClientMock.create(); const userActionService = createUserActionServiceMock(); -const context = {} as RequestHandlerContext; +const context = {} as CasesRequestHandlerContext; const createMock = create as jest.Mock; const updateMock = update as jest.Mock; @@ -57,7 +58,6 @@ describe('createCaseClient()', () => { caseConfigureService, caseService, connectorMappingsService, - context, request, savedObjectsClient, userActionService, @@ -68,7 +68,6 @@ describe('createCaseClient()', () => { caseConfigureService, caseService, connectorMappingsService, - context, request, savedObjectsClient, userActionService, @@ -79,7 +78,6 @@ describe('createCaseClient()', () => { caseConfigureService, caseService, connectorMappingsService, - context, request, savedObjectsClient, userActionService, diff --git a/x-pack/plugins/case/server/client/index.ts b/x-pack/plugins/case/server/client/index.ts index 70eb3282dd243..c4eb1334eb1e4 100644 --- a/x-pack/plugins/case/server/client/index.ts +++ b/x-pack/plugins/case/server/client/index.ts @@ -30,7 +30,6 @@ export const createCaseClient = ({ caseConfigureService, caseService, connectorMappingsService, - context, request, savedObjectsClient, userActionService, @@ -40,7 +39,6 @@ export const createCaseClient = ({ caseConfigureService, caseService, connectorMappingsService, - context, request, savedObjectsClient, userActionService, @@ -50,7 +48,6 @@ export const createCaseClient = ({ caseConfigureService, caseService, connectorMappingsService, - context, request, savedObjectsClient, userActionService, @@ -61,7 +58,6 @@ export const createCaseClient = ({ caseConfigureService, caseService, connectorMappingsService, - context, request, savedObjectsClient, userActionService, diff --git a/x-pack/plugins/case/server/client/mocks.ts b/x-pack/plugins/case/server/client/mocks.ts index 78cb7f71cef4c..2db00ff8ca6d6 100644 --- a/x-pack/plugins/case/server/client/mocks.ts +++ b/x-pack/plugins/case/server/client/mocks.ts @@ -5,7 +5,7 @@ */ import { omit } from 'lodash/fp'; -import { KibanaRequest, RequestHandlerContext } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { loggingSystemMock } from '../../../../../src/core/server/mocks'; import { actionsClientMock } from '../../../actions/server/mocks'; import { @@ -19,6 +19,7 @@ import { CaseClient } from './types'; import { authenticationMock } from '../routes/api/__fixtures__'; import { createCaseClient } from '.'; import { getActions } from '../routes/api/__mocks__/request_responses'; +import type { CasesRequestHandlerContext } from '../types'; export type CaseClientMock = jest.Mocked; export const createCaseClientMock = (): CaseClientMock => ({ @@ -92,7 +93,7 @@ export const createCaseClientWithMockSavedObjectsClient = async ({ connectorMappingsService, userActionService, alertsService, - context: (omit(omitFromContext, context) as unknown) as RequestHandlerContext, + context: (omit(omitFromContext, context) as unknown) as CasesRequestHandlerContext, }); return { client: caseClient, diff --git a/x-pack/plugins/case/server/client/types.ts b/x-pack/plugins/case/server/client/types.ts index ec83f1ec1ff7d..fe80b1ba46a7e 100644 --- a/x-pack/plugins/case/server/client/types.ts +++ b/x-pack/plugins/case/server/client/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, SavedObjectsClientContract, RequestHandlerContext } from 'kibana/server'; +import { KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; import { ActionsClient } from '../../../actions/server'; import { CasePostRequest, @@ -23,6 +23,8 @@ import { AlertServiceContract, } from '../services'; import { ConnectorMappingsServiceSetup } from '../services/connector_mappings'; +import type { CasesRequestHandlerContext } from '../types'; + export interface CaseClientCreate { theCase: CasePostRequest; } @@ -43,8 +45,6 @@ export interface CaseClientUpdateAlertsStatus { status: CaseStatuses; } -type PartialExceptFor = Partial & Pick; - export interface CaseClientFactoryArguments { caseConfigureService: CaseConfigureServiceSetup; caseService: CaseServiceSetup; @@ -53,7 +53,7 @@ export interface CaseClientFactoryArguments { savedObjectsClient: SavedObjectsClientContract; userActionService: CaseUserActionServiceSetup; alertsService: AlertServiceContract; - context?: PartialExceptFor; + context?: Omit; } export interface ConfigureFields { diff --git a/x-pack/plugins/case/server/connectors/case/index.ts b/x-pack/plugins/case/server/connectors/case/index.ts index 540f0b0a66728..07bfb70f1f2a1 100644 --- a/x-pack/plugins/case/server/connectors/case/index.ts +++ b/x-pack/plugins/case/server/connectors/case/index.ts @@ -6,7 +6,7 @@ import { curry } from 'lodash'; -import { KibanaRequest, RequestHandlerContext } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { ActionTypeExecutorResult } from '../../../../actions/common'; import { CasePatchRequest, CasePostRequest } from '../../../common/api'; import { createCaseClient } from '../../client'; @@ -18,6 +18,7 @@ import { CaseActionTypeExecutorOptions, } from './types'; import * as i18n from './translations'; +import type { CasesRequestHandlerContext } from '../../types'; import { GetActionTypeParams } from '..'; @@ -77,7 +78,7 @@ async function executor( userActionService, alertsService, // TODO: When case connector is enabled we should figure out how to pass the context. - context: {} as RequestHandlerContext, + context: {} as CasesRequestHandlerContext, }); if (!supportedSubActions.includes(subAction)) { diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts index 915656895e8c8..0b9712e78c2bc 100644 --- a/x-pack/plugins/case/server/plugin.ts +++ b/x-pack/plugins/case/server/plugin.ts @@ -5,14 +5,7 @@ */ import { first, map } from 'rxjs/operators'; -import { - IContextProvider, - KibanaRequest, - Logger, - PluginInitializerContext, - RequestHandler, - RequestHandlerContext, -} from 'kibana/server'; +import { IContextProvider, KibanaRequest, Logger, PluginInitializerContext } from 'kibana/server'; import { CoreSetup, CoreStart } from 'src/core/server'; import { SecurityPluginSetup } from '../../security/server'; @@ -42,6 +35,7 @@ import { } from './services'; import { createCaseClient } from './client'; import { registerConnectors } from './connectors'; +import type { CasesRequestHandlerContext } from './types'; function createConfig$(context: PluginInitializerContext) { return context.config.create().pipe(map((config) => config)); @@ -91,7 +85,7 @@ export class CasePlugin { this.userActionService = await new CaseUserActionService(this.log).setup(); this.alertsService = new AlertService(); - core.http.registerRouteHandlerContext( + core.http.registerRouteHandlerContext( APP_ID, this.createRouteHandlerContext({ core, @@ -103,7 +97,7 @@ export class CasePlugin { }) ); - const router = core.http.createRouter(); + const router = core.http.createRouter(); initCaseApi({ caseService: this.caseService, caseConfigureService: this.caseConfigureService, @@ -128,7 +122,7 @@ export class CasePlugin { this.alertsService!.initialize(core.elasticsearch.client); const getCaseClientWithRequestAndContext = async ( - context: RequestHandlerContext, + context: CasesRequestHandlerContext, request: KibanaRequest ) => { return createCaseClient({ @@ -166,7 +160,7 @@ export class CasePlugin { connectorMappingsService: ConnectorMappingsServiceSetup; userActionService: CaseUserActionServiceSetup; alertsService: AlertServiceContract; - }): IContextProvider, typeof APP_ID> => { + }): IContextProvider => { return async (context, request) => { const [{ savedObjects }] = await core.getStartServices(); return { diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts index b2d232dbb7cca..40911496d6494 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext, KibanaRequest } from 'src/core/server'; +import { KibanaRequest } from 'src/core/server'; import { loggingSystemMock, elasticsearchServiceMock } from 'src/core/server/mocks'; import { actionsClientMock } from '../../../../../actions/server/mocks'; import { createCaseClient } from '../../../client'; @@ -16,6 +16,7 @@ import { } from '../../../services'; import { getActions } from '../__mocks__/request_responses'; import { authenticationMock } from '../__fixtures__'; +import type { CasesRequestHandlerContext } from '../../../types'; export const createRouteContext = async (client: any, badAuth = false) => { const actionsMock = actionsClientMock.create(); @@ -49,7 +50,7 @@ export const createRouteContext = async (client: any, badAuth = false) => { getSignalsIndex: () => '.siem-signals', }), }, - } as unknown) as RequestHandlerContext; + } as unknown) as CasesRequestHandlerContext; const connectorMappingsService = await connectorMappingsServicePlugin.setup(); const caseClient = createCaseClient({ diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts index c77d2bd45a795..b744a6dc04810 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts @@ -59,6 +59,7 @@ describe('GET connectors', () => { }) ); + // @ts-expect-error context.actions = undefined; const res = await routeHandler(context, req, kibanaResponseFactory); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts index ff0939fdcce1f..4746a203b40f4 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandler, RequestHandlerContext } from 'src/core/server'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; import { httpServerMock } from 'src/core/server/mocks'; import { @@ -17,6 +17,7 @@ import { import { initPostPushToService } from './post_push_to_service'; import { executePushResponse, newPostPushRequest } from '../../__mocks__/request_responses'; import { CASE_CONFIGURE_PUSH_URL } from '../../../../../common/constants'; +import type { CasesRequestHandlerContext } from '../../../../types'; describe('Post push to service', () => { let routeHandler: RequestHandler; @@ -28,7 +29,7 @@ describe('Post push to service', () => { }, body: newPostPushRequest, }); - let context: RequestHandlerContext; + let context: CasesRequestHandlerContext; beforeAll(async () => { routeHandler = await createRoute(initPostPushToService, 'post'); const spyOnDate = jest.spyOn(global, 'Date') as jest.SpyInstance<{}, []>; @@ -67,7 +68,7 @@ describe('Post push to service', () => { }; }, }, - } as unknown) as RequestHandlerContext; + } as unknown) as CasesRequestHandlerContext; const res = await routeHandler(betterContext, req, kibanaResponseFactory); @@ -81,7 +82,7 @@ describe('Post push to service', () => { const betterContext = ({ ...context, case: null, - } as unknown) as RequestHandlerContext; + } as unknown) as CasesRequestHandlerContext; const res = await routeHandler(betterContext, req, kibanaResponseFactory); expect(res.status).toEqual(400); @@ -94,7 +95,7 @@ describe('Post push to service', () => { const betterContext = ({ ...context, actions: null, - } as unknown) as RequestHandlerContext; + } as unknown) as CasesRequestHandlerContext; const res = await routeHandler(betterContext, req, kibanaResponseFactory); expect(res.status).toEqual(404); diff --git a/x-pack/plugins/case/server/routes/api/types.ts b/x-pack/plugins/case/server/routes/api/types.ts index 0b93d844fe9ab..c01ec3d232a0a 100644 --- a/x-pack/plugins/case/server/routes/api/types.ts +++ b/x-pack/plugins/case/server/routes/api/types.ts @@ -4,19 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; -import { +import type { CaseConfigureServiceSetup, CaseServiceSetup, CaseUserActionServiceSetup, ConnectorMappingsServiceSetup, } from '../../services'; +import type { CasesRouter } from '../../types'; + export interface RouteDeps { caseConfigureService: CaseConfigureServiceSetup; caseService: CaseServiceSetup; connectorMappingsService: ConnectorMappingsServiceSetup; - router: IRouter; + router: CasesRouter; userActionService: CaseUserActionServiceSetup; } diff --git a/x-pack/plugins/case/server/types.ts b/x-pack/plugins/case/server/types.ts index d0dfc26aa7b8c..34be3a89716a5 100644 --- a/x-pack/plugins/case/server/types.ts +++ b/x-pack/plugins/case/server/types.ts @@ -4,19 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AppRequestContext } from '../../security_solution/server/types'; +import type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { AppRequestContext } from '../../security_solution/server'; +import type { ActionsApiRequestHandlerContext } from '../../actions/server'; import { CaseClient } from './client'; export interface CaseRequestContext { getCaseClient: () => CaseClient; } -declare module 'src/core/server' { - interface RequestHandlerContext { - case?: CaseRequestContext; - // TODO: Remove when triggers_ui do not import case's types. - // PR https://github.com/elastic/kibana/pull/84587. - securitySolution?: AppRequestContext; - } +/** + * @internal + */ +export interface CasesRequestHandlerContext extends RequestHandlerContext { + case: CaseRequestContext; + actions: ActionsApiRequestHandlerContext; + // TODO: Remove when triggers_ui do not import case's types. + // PR https://github.com/elastic/kibana/pull/84587. + securitySolution: AppRequestContext; } + +/** + * @internal + */ +export type CasesRouter = IRouter; diff --git a/x-pack/plugins/cross_cluster_replication/server/plugin.ts b/x-pack/plugins/cross_cluster_replication/server/plugin.ts index d40a53f289873..3fb488dde4c3d 100644 --- a/x-pack/plugins/cross_cluster_replication/server/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/server/plugin.ts @@ -4,12 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -declare module 'src/core/server' { - interface RequestHandlerContext { - crossClusterReplication?: CrossClusterReplicationContext; - } -} - import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; @@ -20,12 +14,11 @@ import { Logger, PluginInitializerContext, LegacyAPICaller, - ILegacyScopedClusterClient, } from 'src/core/server'; import { Index } from '../../index_management/server'; import { PLUGIN } from '../common/constants'; -import { Dependencies } from './types'; +import type { Dependencies, CcrRequestHandlerContext } from './types'; import { registerApiRoutes } from './routes'; import { License } from './services'; import { elasticsearchJsPlugin } from './client/elasticsearch_ccr'; @@ -33,10 +26,6 @@ import { CrossClusterReplicationConfig } from './config'; import { isEsError } from './shared_imports'; import { formatEsError } from './lib/format_es_error'; -interface CrossClusterReplicationContext { - client: ILegacyScopedClusterClient; -} - async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { const [core] = await getStartServices(); // Extend the elasticsearchJs client with additional endpoints. @@ -137,12 +126,15 @@ export class CrossClusterReplicationServerPlugin implements Plugin { - this.ccrEsClient = this.ccrEsClient ?? (await getCustomEsClient(getStartServices)); - return { - client: this.ccrEsClient.asScoped(request), - }; - }); + http.registerRouteHandlerContext( + 'crossClusterReplication', + async (ctx, request) => { + this.ccrEsClient = this.ccrEsClient ?? (await getCustomEsClient(getStartServices)); + return { + client: this.ccrEsClient.asScoped(request), + }; + } + ); registerApiRoutes({ router: http.createRouter(), diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts index 521de77180974..463df5cc20794 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts @@ -49,7 +49,7 @@ export const registerUpdateRoute = ({ try { const { follower_indices: followerIndices, - } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.info', { id }); + } = await context.crossClusterReplication.client.callAsCurrentUser('ccr.info', { id }); const followerIndexInfo = followerIndices && followerIndices[0]; diff --git a/x-pack/plugins/cross_cluster_replication/server/services/license.ts b/x-pack/plugins/cross_cluster_replication/server/services/license.ts index 5424092a01ee5..cccc54dcc058a 100644 --- a/x-pack/plugins/cross_cluster_replication/server/services/license.ts +++ b/x-pack/plugins/cross_cluster_replication/server/services/license.ts @@ -4,12 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { Logger } from 'src/core/server'; -import { - KibanaRequest, - KibanaResponseFactory, - RequestHandler, - RequestHandlerContext, -} from 'src/core/server'; +import { KibanaRequest, KibanaResponseFactory, RequestHandler } from 'src/core/server'; +import type { CcrRequestHandlerContext } from '../types'; import { LicensingPluginSetup } from '../../../licensing/server'; import { LicenseType } from '../../../licensing/common/types'; @@ -59,11 +55,11 @@ export class License { }); } - guardApiRoute(handler: RequestHandler) { + guardApiRoute(handler: RequestHandler) { const license = this; return function licenseCheck( - ctx: RequestHandlerContext, + ctx: CcrRequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory ) { diff --git a/x-pack/plugins/cross_cluster_replication/server/types.ts b/x-pack/plugins/cross_cluster_replication/server/types.ts index 62c96b48c4373..48ded67566b30 100644 --- a/x-pack/plugins/cross_cluster_replication/server/types.ts +++ b/x-pack/plugins/cross_cluster_replication/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import { IRouter, ILegacyScopedClusterClient, RequestHandlerContext } from 'src/core/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { LicensingPluginSetup } from '../../licensing/server'; import { IndexManagementPluginSetup } from '../../index_management/server'; @@ -21,10 +21,24 @@ export interface Dependencies { } export interface RouteDependencies { - router: IRouter; + router: CcrPluginRouter; license: License; lib: { isEsError: typeof isEsError; formatEsError: typeof formatEsError; }; } + +/** + * @internal + */ +export interface CcrRequestHandlerContext extends RequestHandlerContext { + crossClusterReplication: { + client: ILegacyScopedClusterClient; + }; +} + +/** + * @internal + */ +type CcrPluginRouter = IRouter; diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index 35a838f253ac6..cff0ee3efd738 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -21,6 +21,7 @@ import { eqlSearchStrategyProvider, } from './search'; import { getUiSettings } from './ui_settings'; +import type { DataEnhancedRequestHandlerContext } from './type'; interface SetupDependencies { data: DataPluginSetup; @@ -73,7 +74,7 @@ export class EnhancedDataServerPlugin }, }); - const router = core.http.createRouter(); + const router = core.http.createRouter(); registerSessionRoutes(router, this.logger); this.sessionService.setup(core, { diff --git a/x-pack/plugins/data_enhanced/server/routes/mocks.ts b/x-pack/plugins/data_enhanced/server/routes/mocks.ts index 3e7b89ed2cca6..4bad563bf393b 100644 --- a/x-pack/plugins/data_enhanced/server/routes/mocks.ts +++ b/x-pack/plugins/data_enhanced/server/routes/mocks.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'kibana/server'; +import type { DataRequestHandlerContext } from '../../../../../src/plugins/data/server'; import { coreMock } from '../../../../../src/core/server/mocks'; export function createSearchRequestHandlerContext() { @@ -22,5 +22,5 @@ export function createSearchRequestHandlerContext() { update: jest.fn(), }, }, - } as unknown) as jest.Mocked; + } as unknown) as jest.Mocked; } diff --git a/x-pack/plugins/data_enhanced/server/routes/session.test.ts b/x-pack/plugins/data_enhanced/server/routes/session.test.ts index 0251660202597..c4433b562e97a 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.test.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.test.ts @@ -5,15 +5,19 @@ */ import type { MockedKeys } from '@kbn/utility-types/jest'; -import type { CoreSetup, Logger, RequestHandlerContext } from 'kibana/server'; + +import type { CoreSetup, Logger } from 'kibana/server'; import { coreMock, httpServerMock } from '../../../../../src/core/server/mocks'; -import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; +import type { + PluginStart as DataPluginStart, + DataRequestHandlerContext, +} from '../../../../../src/plugins/data/server'; import { createSearchRequestHandlerContext } from './mocks'; import { registerSessionRoutes } from './session'; describe('registerSessionRoutes', () => { let mockCoreSetup: MockedKeys>; - let mockContext: jest.Mocked; + let mockContext: jest.Mocked; let mockLogger: Logger; beforeEach(() => { diff --git a/x-pack/plugins/data_enhanced/server/routes/session.ts b/x-pack/plugins/data_enhanced/server/routes/session.ts index 9e61dd39c83b8..cbf683bd18fd2 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.ts @@ -5,10 +5,11 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter, Logger } from 'src/core/server'; +import { Logger } from 'src/core/server'; import { reportServerError } from '../../../../../src/plugins/kibana_utils/server'; +import { DataEnhancedPluginRouter } from '../type'; -export function registerSessionRoutes(router: IRouter, logger: Logger): void { +export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: Logger): void { router.post( { path: '/internal/session', diff --git a/x-pack/plugins/data_enhanced/server/type.ts b/x-pack/plugins/data_enhanced/server/type.ts new file mode 100644 index 0000000000000..a0dcbd81a5dde --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/type.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import type { IRouter } from 'kibana/server'; +import type { DataRequestHandlerContext } from '../../../../src/plugins/data/server'; + +/** + * @internal + */ +export type DataEnhancedRequestHandlerContext = DataRequestHandlerContext; + +/** + * @internal + */ +export type DataEnhancedPluginRouter = IRouter; diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index 03125f3005c3d..3bf726de71856 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -15,11 +15,11 @@ import { LegacyClusterClient, SharedGlobalConfig, IContextProvider, - RequestHandler, } from 'src/core/server'; import { SpacesPluginStart } from '../../spaces/server'; -import { +import type { + EventLogRequestHandlerContext, IEventLogConfig, IEventLogService, IEventLogger, @@ -97,10 +97,13 @@ export class Plugin implements CorePlugin( + 'eventLog', + this.createRouteHandlerContext() + ); // Routes - const router = core.http.createRouter(); + const router = core.http.createRouter(); // Register routes findRoute(router, this.systemLogger); findByIdsRoute(router, this.systemLogger); @@ -169,7 +172,7 @@ export class Plugin implements CorePlugin, + EventLogRequestHandlerContext, 'eventLog' > => { return async (context, request) => { diff --git a/x-pack/plugins/event_log/server/routes/find.ts b/x-pack/plugins/event_log/server/routes/find.ts index 50785de72cfc5..aa882fb002752 100644 --- a/x-pack/plugins/event_log/server/routes/find.ts +++ b/x-pack/plugins/event_log/server/routes/find.ts @@ -5,15 +5,13 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, +import type { KibanaRequest, IKibanaResponse, KibanaResponseFactory, Logger, } from 'src/core/server'; - +import type { EventLogRouter, EventLogRequestHandlerContext } from '../types'; import { BASE_EVENT_LOG_API_PATH } from '../../common'; import { findOptionsSchema, FindOptionsType } from '../event_log_client'; @@ -22,7 +20,7 @@ const paramSchema = schema.object({ id: schema.string(), }); -export const findRoute = (router: IRouter, systemLogger: Logger) => { +export const findRoute = (router: EventLogRouter, systemLogger: Logger) => { router.get( { path: `${BASE_EVENT_LOG_API_PATH}/{type}/{id}/_find`, @@ -32,7 +30,7 @@ export const findRoute = (router: IRouter, systemLogger: Logger) => { }, }, router.handleLegacyErrors(async function ( - context: RequestHandlerContext, + context: EventLogRequestHandlerContext, req: KibanaRequest, FindOptionsType, unknown>, res: KibanaResponseFactory ): Promise { diff --git a/x-pack/plugins/event_log/server/routes/find_by_ids.ts b/x-pack/plugins/event_log/server/routes/find_by_ids.ts index a7ee0f35ac59e..a846c93eb95ed 100644 --- a/x-pack/plugins/event_log/server/routes/find_by_ids.ts +++ b/x-pack/plugins/event_log/server/routes/find_by_ids.ts @@ -5,14 +5,13 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, +import type { KibanaRequest, IKibanaResponse, KibanaResponseFactory, Logger, } from 'src/core/server'; +import type { EventLogRouter, EventLogRequestHandlerContext } from '../types'; import { BASE_EVENT_LOG_API_PATH } from '../../common'; import { findOptionsSchema, FindOptionsType } from '../event_log_client'; @@ -25,7 +24,7 @@ const bodySchema = schema.object({ ids: schema.arrayOf(schema.string(), { defaultValue: [] }), }); -export const findByIdsRoute = (router: IRouter, systemLogger: Logger) => { +export const findByIdsRoute = (router: EventLogRouter, systemLogger: Logger) => { router.post( { path: `${BASE_EVENT_LOG_API_PATH}/{type}/_find`, @@ -36,7 +35,7 @@ export const findByIdsRoute = (router: IRouter, systemLogger: Logger) => { }, }, router.handleLegacyErrors(async function ( - context: RequestHandlerContext, + context: EventLogRequestHandlerContext, req: KibanaRequest, FindOptionsType, TypeOf>, res: KibanaResponseFactory ): Promise { diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts index ff2ae81632923..e995e979a0808 100644 --- a/x-pack/plugins/event_log/server/types.ts +++ b/x-pack/plugins/event_log/server/types.ts @@ -6,7 +6,7 @@ import { Observable } from 'rxjs'; import { schema, TypeOf } from '@kbn/config-schema'; -import { KibanaRequest } from 'src/core/server'; +import type { IRouter, KibanaRequest, RequestHandlerContext } from 'src/core/server'; export { IEvent, IValidatedEvent, EventSchema, ECS_VERSION } from '../generated/schemas'; import { IEvent } from '../generated/schemas'; @@ -26,14 +26,6 @@ export const ConfigSchema = schema.object({ export type IEventLogConfig = TypeOf; export type IEventLogConfig$ = Observable>; -declare module 'src/core/server' { - interface RequestHandlerContext { - eventLog?: { - getEventLogClient: () => IEventLogClient; - }; - } -} - // the object exposed by plugin.setup() export interface IEventLogService { isEnabled(): boolean; @@ -63,3 +55,22 @@ export interface IEventLogger { startTiming(event: IEvent): void; stopTiming(event: IEvent): void; } + +/** + * @internal + */ +export interface EventLogApiRequestHandlerContext { + getEventLogClient(): IEventLogClient; +} + +/** + * @internal + */ +export interface EventLogRequestHandlerContext extends RequestHandlerContext { + eventLog: EventLogApiRequestHandlerContext; +} + +/** + * @internal + */ +export type EventLogRouter = IRouter; diff --git a/x-pack/plugins/features/server/routes/index.ts b/x-pack/plugins/features/server/routes/index.ts index b2bfa8b0296b7..cd6d220961831 100644 --- a/x-pack/plugins/features/server/routes/index.ts +++ b/x-pack/plugins/features/server/routes/index.ts @@ -5,14 +5,14 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../../../../src/core/server'; +import type { FeaturesPluginRouter } from '../types'; import { FeatureRegistry } from '../feature_registry'; /** * Describes parameters used to define HTTP routes. */ export interface RouteDefinitionParams { - router: IRouter; + router: FeaturesPluginRouter; featureRegistry: FeatureRegistry; } diff --git a/x-pack/plugins/features/server/types.ts b/x-pack/plugins/features/server/types.ts new file mode 100644 index 0000000000000..d42fa1c498d48 --- /dev/null +++ b/x-pack/plugins/features/server/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import type { RequestHandlerContext, IRouter } from 'src/core/server'; +import type { LicensingApiRequestHandlerContext } from '../../licensing/server'; + +/** + * @internal + */ +export interface FeaturesRequestHandlerContext extends RequestHandlerContext { + licensing: LicensingApiRequestHandlerContext; +} + +/** + * @internal + */ +export type FeaturesPluginRouter = IRouter; diff --git a/x-pack/plugins/global_search/server/mocks.ts b/x-pack/plugins/global_search/server/mocks.ts index 88be7f6e861a1..f0498302808e4 100644 --- a/x-pack/plugins/global_search/server/mocks.ts +++ b/x-pack/plugins/global_search/server/mocks.ts @@ -9,9 +9,11 @@ import { GlobalSearchPluginSetup, GlobalSearchPluginStart, RouteHandlerGlobalSearchContext, + GlobalSearchRequestHandlerContext, } from './types'; import { searchServiceMock } from './services/search_service.mock'; import { contextMock } from './services/context.mock'; +import { coreMock } from '../../../../src/core/server/mocks'; const createSetupMock = (): jest.Mocked => { const searchMock = searchServiceMock.createSetupContract(); @@ -41,9 +43,21 @@ const createRouteHandlerContextMock = (): jest.Mocked => { + const handlerContextMock = { + find: jest.fn(), + getSearchableTypes: jest.fn(), + }; + + handlerContextMock.find.mockReturnValue(of([])); + + return { core: coreMock.createRequestHandlerContext(), globalSearch: handlerContextMock }; +}; + export const globalSearchPluginMock = { createSetupContract: createSetupMock, createStartContract: createStartMock, createRouteHandlerContext: createRouteHandlerContextMock, createProviderContext: contextMock.create, + createRequestHandlerContext: createRequestHandlerContextMock, }; diff --git a/x-pack/plugins/global_search/server/plugin.ts b/x-pack/plugins/global_search/server/plugin.ts index 9d6844dde50f0..29c63efd64df0 100644 --- a/x-pack/plugins/global_search/server/plugin.ts +++ b/x-pack/plugins/global_search/server/plugin.ts @@ -14,16 +14,10 @@ import { registerRoutes } from './routes'; import { GlobalSearchPluginSetup, GlobalSearchPluginStart, - RouteHandlerGlobalSearchContext, + GlobalSearchRequestHandlerContext, } from './types'; import { GlobalSearchConfigType } from './config'; -declare module 'src/core/server' { - interface RequestHandlerContext { - globalSearch?: RouteHandlerGlobalSearchContext; - } -} - // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface GlobalSearchPluginSetupDeps {} export interface GlobalSearchPluginStartDeps { @@ -56,12 +50,15 @@ export class GlobalSearchPlugin registerRoutes(core.http.createRouter()); - core.http.registerRouteHandlerContext('globalSearch', (_, req) => { - return { - find: (term, options) => this.searchServiceStart!.find(term, options, req), - getSearchableTypes: () => this.searchServiceStart!.getSearchableTypes(req), - }; - }); + core.http.registerRouteHandlerContext( + 'globalSearch', + (_, req) => { + return { + find: (term, options) => this.searchServiceStart!.find(term, options, req), + getSearchableTypes: () => this.searchServiceStart!.getSearchableTypes(req), + }; + } + ); return { registerResultProvider, diff --git a/x-pack/plugins/global_search/server/routes/find.ts b/x-pack/plugins/global_search/server/routes/find.ts index 0b82a035348ed..f9bc0d8698821 100644 --- a/x-pack/plugins/global_search/server/routes/find.ts +++ b/x-pack/plugins/global_search/server/routes/find.ts @@ -6,10 +6,10 @@ import { reduce, map } from 'rxjs/operators'; import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import { GlobalSearchRouter } from '../types'; import { GlobalSearchFindError } from '../../common/errors'; -export const registerInternalFindRoute = (router: IRouter) => { +export const registerInternalFindRoute = (router: GlobalSearchRouter) => { router.post( { path: '/internal/global_search/find', diff --git a/x-pack/plugins/global_search/server/routes/get_searchable_types.ts b/x-pack/plugins/global_search/server/routes/get_searchable_types.ts index f9cc69e4a28ae..c2e58b0cd9ba7 100644 --- a/x-pack/plugins/global_search/server/routes/get_searchable_types.ts +++ b/x-pack/plugins/global_search/server/routes/get_searchable_types.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import { GlobalSearchRouter } from '../types'; -export const registerInternalSearchableTypesRoute = (router: IRouter) => { +export const registerInternalSearchableTypesRoute = (router: GlobalSearchRouter) => { router.get( { path: '/internal/global_search/searchable_types', diff --git a/x-pack/plugins/global_search/server/routes/index.ts b/x-pack/plugins/global_search/server/routes/index.ts index 0eeb443b72b53..7f11f01cbc46a 100644 --- a/x-pack/plugins/global_search/server/routes/index.ts +++ b/x-pack/plugins/global_search/server/routes/index.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import { GlobalSearchRouter } from '../types'; import { registerInternalFindRoute } from './find'; import { registerInternalSearchableTypesRoute } from './get_searchable_types'; -export const registerRoutes = (router: IRouter) => { +export const registerRoutes = (router: GlobalSearchRouter) => { registerInternalFindRoute(router); registerInternalSearchableTypesRoute(router); }; diff --git a/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts b/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts index c37bcdbf84743..db46cdfff360c 100644 --- a/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts +++ b/x-pack/plugins/global_search/server/routes/integration_tests/find.test.ts @@ -41,13 +41,12 @@ describe('POST /internal/global_search/find', () => { ({ server, httpSetup } = await setupServer(pluginId)); globalSearchHandlerContext = globalSearchPluginMock.createRouteHandlerContext(); - httpSetup.registerRouteHandlerContext( - pluginId, - 'globalSearch', - () => globalSearchHandlerContext - ); + httpSetup.registerRouteHandlerContext< + ReturnType, + 'globalSearch' + >(pluginId, 'globalSearch', () => globalSearchHandlerContext); - const router = httpSetup.createRouter('/'); + const router = httpSetup.createRouter('/'); registerInternalFindRoute(router); diff --git a/x-pack/plugins/global_search/server/routes/integration_tests/get_searchable_types.test.ts b/x-pack/plugins/global_search/server/routes/integration_tests/get_searchable_types.test.ts index b3b6862599d6d..66528e4fbe855 100644 --- a/x-pack/plugins/global_search/server/routes/integration_tests/get_searchable_types.test.ts +++ b/x-pack/plugins/global_search/server/routes/integration_tests/get_searchable_types.test.ts @@ -24,13 +24,14 @@ describe('GET /internal/global_search/searchable_types', () => { ({ server, httpSetup } = await setupServer(pluginId)); globalSearchHandlerContext = globalSearchPluginMock.createRouteHandlerContext(); - httpSetup.registerRouteHandlerContext( - pluginId, - 'globalSearch', - () => globalSearchHandlerContext - ); - - const router = httpSetup.createRouter('/'); + httpSetup.registerRouteHandlerContext< + ReturnType, + 'globalSearch' + >(pluginId, 'globalSearch', () => globalSearchHandlerContext); + + const router = httpSetup.createRouter< + ReturnType + >('/'); registerInternalSearchableTypesRoute(router); diff --git a/x-pack/plugins/global_search/server/types.ts b/x-pack/plugins/global_search/server/types.ts index 48c40fdb66e13..17b3505041f06 100644 --- a/x-pack/plugins/global_search/server/types.ts +++ b/x-pack/plugins/global_search/server/types.ts @@ -5,12 +5,14 @@ */ import { Observable } from 'rxjs'; -import { +import type { ISavedObjectTypeRegistry, ILegacyScopedClusterClient, IUiSettingsClient, SavedObjectsClientContract, Capabilities, + IRouter, + RequestHandlerContext, } from 'src/core/server'; import { GlobalSearchBatchedResults, @@ -24,6 +26,17 @@ import { SearchServiceSetup, SearchServiceStart } from './services'; export type GlobalSearchPluginSetup = Pick; export type GlobalSearchPluginStart = Pick; +/** + * @internal + */ +export interface GlobalSearchRequestHandlerContext extends RequestHandlerContext { + globalSearch: RouteHandlerGlobalSearchContext; +} + +/** + * @internal + */ +export type GlobalSearchRouter = IRouter; /** * globalSearch route handler context. * diff --git a/x-pack/plugins/index_management/server/plugin.ts b/x-pack/plugins/index_management/server/plugin.ts index 99facacacfe4c..3717e7e94d29f 100644 --- a/x-pack/plugins/index_management/server/plugin.ts +++ b/x-pack/plugins/index_management/server/plugin.ts @@ -4,19 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -declare module 'kibana/server' { - interface RequestHandlerContext { - dataManagement?: DataManagementContext; - } -} - import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin, Logger, PluginInitializerContext, - ILegacyScopedClusterClient, ILegacyCustomClusterClient, } from 'src/core/server'; @@ -26,10 +19,7 @@ import { ApiRoutes } from './routes'; import { License, IndexDataEnricher } from './services'; import { isEsError, handleEsError, parseEsError } from './shared_imports'; import { elasticsearchJsPlugin } from './client/elasticsearch'; - -export interface DataManagementContext { - client: ILegacyScopedClusterClient; -} +import type { IndexManagementRequestHandlerContext } from './types'; export interface IndexManagementPluginSetup { indexDataEnricher: { @@ -61,7 +51,7 @@ export class IndexMgmtServerPlugin implements Plugin(); this.license.setup( { @@ -92,14 +82,17 @@ export class IndexMgmtServerPlugin implements Plugin { - this.dataManagementESClient = - this.dataManagementESClient ?? (await getCustomEsClient(getStartServices)); + http.registerRouteHandlerContext( + 'dataManagement', + async (ctx, request) => { + this.dataManagementESClient = + this.dataManagementESClient ?? (await getCustomEsClient(getStartServices)); - return { - client: this.dataManagementESClient.asScoped(request), - }; - }); + return { + client: this.dataManagementESClient.asScoped(request), + }; + } + ); this.apiRoutes.setup({ router, diff --git a/x-pack/plugins/index_management/server/services/license.ts b/x-pack/plugins/index_management/server/services/license.ts index 9b68acd073c4a..22497a9d45ecd 100644 --- a/x-pack/plugins/index_management/server/services/license.ts +++ b/x-pack/plugins/index_management/server/services/license.ts @@ -4,15 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { Logger } from 'src/core/server'; -import { - KibanaRequest, - KibanaResponseFactory, - RequestHandler, - RequestHandlerContext, -} from 'kibana/server'; +import type { KibanaRequest, KibanaResponseFactory, RequestHandler } from 'kibana/server'; import { LicensingPluginSetup } from '../../../licensing/server'; import { LicenseType } from '../../../licensing/common/types'; +import type { IndexManagementRequestHandlerContext } from '../types'; export interface LicenseStatus { isValid: boolean; @@ -53,11 +49,13 @@ export class License { }); } - guardApiRoute(handler: RequestHandler) { + guardApiRoute( + handler: RequestHandler + ) { const license = this; return function licenseCheck( - ctx: RequestHandlerContext, + ctx: Context, request: KibanaRequest, response: KibanaResponseFactory ) { diff --git a/x-pack/plugins/index_management/server/types.ts b/x-pack/plugins/index_management/server/types.ts index 16a6b43af8512..34d03129c62d6 100644 --- a/x-pack/plugins/index_management/server/types.ts +++ b/x-pack/plugins/index_management/server/types.ts @@ -3,7 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { LegacyScopedClusterClient, IRouter } from 'src/core/server'; +import type { + LegacyScopedClusterClient, + ILegacyScopedClusterClient, + IRouter, + RequestHandlerContext, +} from 'src/core/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { LicensingPluginSetup } from '../../licensing/server'; import { SecurityPluginSetup } from '../../security/server'; @@ -17,7 +22,7 @@ export interface Dependencies { } export interface RouteDependencies { - router: IRouter; + router: IndexManagementRouter; license: License; config: { isSecurityEnabled: () => boolean; @@ -31,3 +36,26 @@ export interface RouteDependencies { } export type CallAsCurrentUser = LegacyScopedClusterClient['callAsCurrentUser']; + +export interface DataManagementContext { + client: ILegacyScopedClusterClient; +} + +/** + * @internal + */ +export interface IndexManagementApiRequestHandlerContext { + client: ILegacyScopedClusterClient; +} + +/** + * @internal + */ +export interface IndexManagementRequestHandlerContext extends RequestHandlerContext { + dataManagement: IndexManagementApiRequestHandlerContext; +} + +/** + * @internal + */ +export type IndexManagementRouter = IRouter; diff --git a/x-pack/plugins/infra/server/lib/adapters/fields/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/fields/adapter_types.ts index a1630281c2f75..d6b069997b618 100644 --- a/x-pack/plugins/infra/server/lib/adapters/fields/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/fields/adapter_types.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; export interface FieldsAdapter { getIndexFields( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, indices: string ): Promise; } diff --git a/x-pack/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts index 8a9389ed585eb..57345f1353e91 100644 --- a/x-pack/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; import { KibanaFramework } from '../framework/kibana_framework_adapter'; import { FieldsAdapter, IndexFieldDescriptor } from './adapter_types'; @@ -16,7 +16,7 @@ export class FrameworkFieldsAdapter implements FieldsAdapter { } public async getIndexFields( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, indices: string ): Promise { const indexPatternsService = this.framework.getIndexPatternsService(requestContext); diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts index 7f686b4d7717c..b96b0e5bb0b48 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -23,16 +23,16 @@ import { CoreSetup, IRouter, KibanaRequest, - RequestHandlerContext, KibanaResponseFactory, RouteMethod, } from '../../../../../../../src/core/server'; import { RequestHandler } from '../../../../../../../src/core/server'; import { InfraConfig } from '../../../plugin'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; import { IndexPatternsFetcher, UI_SETTINGS } from '../../../../../../../src/plugins/data/server'; export class KibanaFramework { - public router: IRouter; + public router: IRouter; public plugins: InfraServerPluginSetupDeps; constructor(core: CoreSetup, config: InfraConfig, plugins: InfraServerPluginSetupDeps) { @@ -42,7 +42,7 @@ export class KibanaFramework { public registerRoute( config: InfraRouteConfig, - handler: RequestHandler + handler: RequestHandler ) { const defaultOptions = { tags: ['access:infra'], @@ -88,7 +88,7 @@ export class KibanaFramework { }, }; async function handler( - context: RequestHandlerContext, + context: InfraPluginRequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory ) { @@ -100,7 +100,7 @@ export class KibanaFramework { const gqlResponse = await runHttpQuery([context, request], { method: request.route.method.toUpperCase(), - options: (req: RequestHandlerContext, rawReq: KibanaRequest) => ({ + options: (req: InfraPluginRequestHandlerContext, rawReq: KibanaRequest) => ({ context: { req, rawReq }, schema: gqlSchema, }), @@ -147,48 +147,48 @@ export class KibanaFramework { } callWithRequest( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, endpoint: 'search', options?: CallWithRequestParams ): Promise>; callWithRequest( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, endpoint: 'msearch', options?: CallWithRequestParams ): Promise>; callWithRequest( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, endpoint: 'fieldCaps', options?: CallWithRequestParams ): Promise; callWithRequest( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, endpoint: 'indices.existsAlias', options?: CallWithRequestParams ): Promise; callWithRequest( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, method: 'indices.getAlias', options?: object ): Promise; callWithRequest( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, method: 'indices.get' | 'ml.getBuckets', options?: object ): Promise; callWithRequest( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, method: 'transport.request', options?: CallWithRequestParams ): Promise; callWithRequest( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, endpoint: string, options?: CallWithRequestParams ): Promise; public async callWithRequest( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, endpoint: string, params: CallWithRequestParams ) { @@ -216,7 +216,9 @@ export class KibanaFramework { }); } - public getIndexPatternsService(requestContext: RequestHandlerContext): IndexPatternsFetcher { + public getIndexPatternsService( + requestContext: InfraPluginRequestHandlerContext + ): IndexPatternsFetcher { return new IndexPatternsFetcher(requestContext.core.elasticsearch.client.asCurrentUser, true); } @@ -235,7 +237,7 @@ export class KibanaFramework { } public async makeTSVBRequest( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, rawRequest: KibanaRequest, model: TSVBMetricModel, timerange: { min: number; max: number }, diff --git a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts index 98c42ab7d98ab..ffbc750af14f8 100644 --- a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts @@ -10,8 +10,8 @@ import { constant, identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; import * as runtimeTypes from 'io-ts'; import { compact } from 'lodash'; -import { RequestHandlerContext } from 'src/core/server'; import { JsonArray } from '../../../../../../../src/plugins/kibana_utils/common'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; import { LogEntriesAdapter, LogEntriesParams, @@ -30,7 +30,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { constructor(private readonly framework: KibanaFramework) {} public async getLogEntries( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, fields: string[], params: LogEntriesParams @@ -123,7 +123,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { } public async getContainedLogSummaryBuckets( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, startTimestamp: number, endTimestamp: number, diff --git a/x-pack/plugins/infra/server/lib/adapters/metrics/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/metrics/adapter_types.ts index f786c043ee27c..e20f1ab05fd56 100644 --- a/x-pack/plugins/infra/server/lib/adapters/metrics/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/metrics/adapter_types.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext, KibanaRequest } from 'src/core/server'; +import { KibanaRequest } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; import { NodeDetailsRequest, NodeDetailsMetricData, @@ -23,7 +24,7 @@ export interface InfraMetricsRequestOptions export interface InfraMetricsAdapter { getMetrics( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, options: InfraMetricsRequestOptions, request: KibanaRequest ): Promise; diff --git a/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts index 5718d49ae79d6..2c7d79b1c64d0 100644 --- a/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { flatten, get } from 'lodash'; -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { KibanaRequest } from 'src/core/server'; import { NodeDetailsMetricData } from '../../../../common/http_api/node_details_api'; import { KibanaFramework } from '../framework/kibana_framework_adapter'; import { InfraMetricsAdapter, InfraMetricsRequestOptions } from './adapter_types'; @@ -19,6 +19,7 @@ import { } from '../../../../common/inventory_models/types'; import { calculateMetricInterval } from '../../../utils/calculate_metric_interval'; import { CallWithRequestParams, InfraDatabaseSearchResponse } from '../framework'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; export class KibanaMetricsAdapter implements InfraMetricsAdapter { private framework: KibanaFramework; @@ -28,7 +29,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { } public async getMetrics( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, options: InfraMetricsRequestOptions, rawRequest: KibanaRequest ): Promise { @@ -94,7 +95,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { metricId: InventoryMetric, options: InfraMetricsRequestOptions, nodeField: string, - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, rawRequest: KibanaRequest ) { const createTSVBModel = get(metrics, ['tsvb', metricId]) as TSVBMetricModelCreator | undefined; diff --git a/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts index 2a61e64c94fcd..cfa001788246c 100644 --- a/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; import { InfraSourceStatusAdapter, SourceIndexStatus } from '../../source_status'; import { InfraDatabaseGetIndicesResponse } from '../framework'; import { KibanaFramework } from '../framework/kibana_framework_adapter'; @@ -12,7 +12,7 @@ import { KibanaFramework } from '../framework/kibana_framework_adapter'; export class InfraElasticsearchSourceStatusAdapter implements InfraSourceStatusAdapter { constructor(private readonly framework: KibanaFramework) {} - public async getIndexNames(requestContext: RequestHandlerContext, aliasName: string) { + public async getIndexNames(requestContext: InfraPluginRequestHandlerContext, aliasName: string) { const indexMaps = await Promise.all([ this.framework .callWithRequest(requestContext, 'indices.getAlias', { @@ -34,14 +34,14 @@ export class InfraElasticsearchSourceStatusAdapter implements InfraSourceStatusA ); } - public async hasAlias(requestContext: RequestHandlerContext, aliasName: string) { + public async hasAlias(requestContext: InfraPluginRequestHandlerContext, aliasName: string) { return await this.framework.callWithRequest(requestContext, 'indices.existsAlias', { name: aliasName, }); } public async getIndexStatus( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, indexNames: string ): Promise { return await this.framework diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts index e1657968b3f92..0ecea1f5db32e 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_chart_preview.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; import { InfraSource } from '../../sources'; import { KibanaFramework } from '../../adapters/framework/kibana_framework_adapter'; import { @@ -29,7 +29,7 @@ import { decodeOrThrow } from '../../../../common/runtime_types'; const COMPOSITE_GROUP_SIZE = 40; export async function getChartPreviewData( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceConfiguration: InfraSource, callWithRequest: KibanaFramework['callWithRequest'], alertParams: GetLogAlertsChartPreviewDataAlertParamsSubset, @@ -114,7 +114,7 @@ const addHistogramAggregationToQuery = ( const getUngroupedResults = async ( query: object, - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, callWithRequest: KibanaFramework['callWithRequest'] ) => { return decodeOrThrow(UngroupedSearchQueryResponseRT)( @@ -124,7 +124,7 @@ const getUngroupedResults = async ( const getGroupedResults = async ( query: object, - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, callWithRequest: KibanaFramework['callWithRequest'] ) => { let compositeGroupBuckets: GroupedSearchQueryResponse['aggregations']['groups']['buckets'] = []; diff --git a/x-pack/plugins/infra/server/lib/create_search_client.ts b/x-pack/plugins/infra/server/lib/create_search_client.ts index d79d20b502e94..cc354754c3403 100644 --- a/x-pack/plugins/infra/server/lib/create_search_client.ts +++ b/x-pack/plugins/infra/server/lib/create_search_client.ts @@ -3,12 +3,12 @@ * 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 type { InfraPluginRequestHandlerContext } from '../types'; import { CallWithRequestParams, InfraDatabaseSearchResponse } from './adapters/framework'; import { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; export const createSearchClient = ( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, framework: KibanaFramework ) => ( opts: CallWithRequestParams diff --git a/x-pack/plugins/infra/server/lib/domains/fields_domain.ts b/x-pack/plugins/infra/server/lib/domains/fields_domain.ts index ecbc71f4895c7..a8bd09c28f949 100644 --- a/x-pack/plugins/infra/server/lib/domains/fields_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/fields_domain.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../types'; import { InfraIndexField, InfraIndexType } from '../../graphql/types'; import { FieldsAdapter } from '../adapters/fields'; import { InfraSources } from '../sources'; @@ -16,7 +16,7 @@ export class InfraFieldsDomain { ) {} public async getFields( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string, indexType: InfraIndexType ): Promise { diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index d9f125908b32d..0b1df3abd465a 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; import { JsonObject } from '../../../../../../../src/plugins/kibana_utils/common'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; + import { LogEntriesSummaryBucket, LogEntriesSummaryHighlightsBucket, @@ -68,7 +69,7 @@ export class InfraLogEntriesDomain { ) {} public async getLogEntriesAround( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string, params: LogEntriesAroundParams, columnOverrides?: LogEntriesRequest['columns'] @@ -128,7 +129,7 @@ export class InfraLogEntriesDomain { } public async getLogEntries( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string, params: LogEntriesParams, columnOverrides?: LogEntriesRequest['columns'] @@ -187,7 +188,7 @@ export class InfraLogEntriesDomain { } public async getLogSummaryBucketsBetween( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string, start: number, end: number, @@ -210,7 +211,7 @@ export class InfraLogEntriesDomain { } public async getLogSummaryHighlightBucketsBetween( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string, startTimestamp: number, endTimestamp: number, @@ -256,7 +257,7 @@ export class InfraLogEntriesDomain { } public async getLogEntryDatasets( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, timestampField: string, indexName: string, startTime: number, @@ -297,14 +298,14 @@ export class InfraLogEntriesDomain { export interface LogEntriesAdapter { getLogEntries( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, fields: string[], params: LogEntriesParams ): Promise<{ documents: LogEntryDocument[]; hasMoreBefore?: boolean; hasMoreAfter?: boolean }>; getContainedLogSummaryBuckets( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, startTimestamp: number, endTimestamp: number, diff --git a/x-pack/plugins/infra/server/lib/domains/metrics_domain.ts b/x-pack/plugins/infra/server/lib/domains/metrics_domain.ts index ac76e264ff0ed..0189fa885af0e 100644 --- a/x-pack/plugins/infra/server/lib/domains/metrics_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/metrics_domain.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { KibanaRequest } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../types'; import { InfraMetricsAdapter, InfraMetricsRequestOptions } from '../adapters/metrics/adapter_types'; import { NodeDetailsMetricData } from '../../../common/http_api/node_details_api'; @@ -16,7 +17,7 @@ export class InfraMetricsDomain { } public async getMetrics( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, options: InfraMetricsRequestOptions, rawRequest: KibanaRequest ): Promise { 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 index c8278bd308758..6d6ac68a4d412 100644 --- 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 @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../types'; import { InfraRequestHandlerContext } from '../../types'; import { TracingSpan, startTracingSpan } from '../../../common/performance_tracing'; import { fetchMlJob } from './common'; @@ -73,7 +73,7 @@ async function getCompatibleAnomaliesJobIds( } export async function getMetricsHostsAnomalies( - context: RequestHandlerContext & { infra: Required }, + context: InfraPluginRequestHandlerContext & { infra: Required }, sourceId: string, startTime: number, endTime: number, 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 index c8427ef489c4c..6d8ac7fa00d55 100644 --- 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 @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../types'; import { InfraRequestHandlerContext } from '../../types'; import { TracingSpan, startTracingSpan } from '../../../common/performance_tracing'; import { fetchMlJob } from './common'; @@ -73,7 +73,7 @@ async function getCompatibleAnomaliesJobIds( } export async function getMetricK8sAnomalies( - context: RequestHandlerContext & { infra: Required }, + context: InfraPluginRequestHandlerContext & { infra: Required }, sourceId: string, startTime: number, endTime: number, diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts index 44731fe465d26..c6a4593912280 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; -import { InfraRequestHandlerContext } from '../../types'; +import type { InfraPluginRequestHandlerContext, InfraRequestHandlerContext } from '../../types'; import { TracingSpan, startTracingSpan } from '../../../common/performance_tracing'; import { fetchMlJob, getLogEntryDatasets } from './common'; import { @@ -92,7 +91,7 @@ async function getCompatibleAnomaliesJobIds( } export async function getLogEntryAnomalies( - context: RequestHandlerContext & { infra: Required }, + context: InfraPluginRequestHandlerContext & { infra: Required }, sourceId: string, startTime: number, endTime: number, @@ -291,7 +290,7 @@ async function fetchLogEntryAnomalies( } export async function getLogEntryExamples( - context: RequestHandlerContext & { infra: Required }, + context: InfraPluginRequestHandlerContext & { infra: Required }, sourceId: string, startTime: number, endTime: number, @@ -353,7 +352,7 @@ export async function getLogEntryExamples( } export async function fetchLogEntryExamples( - context: RequestHandlerContext & { infra: Required }, + context: InfraPluginRequestHandlerContext & { infra: Required }, sourceId: string, indices: string, timestampField: string, diff --git a/x-pack/plugins/infra/server/lib/source_status.ts b/x-pack/plugins/infra/server/lib/source_status.ts index c383d01933562..5bfeff23f9aea 100644 --- a/x-pack/plugins/infra/server/lib/source_status.ts +++ b/x-pack/plugins/infra/server/lib/source_status.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../types'; import { InfraSources } from './sources'; export class InfraSourceStatus { @@ -14,7 +14,7 @@ export class InfraSourceStatus { ) {} public async getLogIndexNames( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( @@ -28,7 +28,7 @@ export class InfraSourceStatus { return indexNames; } public async getMetricIndexNames( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( @@ -42,7 +42,7 @@ export class InfraSourceStatus { return indexNames; } public async hasLogAlias( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( @@ -56,7 +56,7 @@ export class InfraSourceStatus { return hasAlias; } public async hasMetricAlias( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( @@ -70,7 +70,7 @@ export class InfraSourceStatus { return hasAlias; } public async getLogIndexStatus( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( @@ -84,7 +84,7 @@ export class InfraSourceStatus { return indexStatus; } public async hasMetricIndices( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( @@ -102,10 +102,13 @@ export class InfraSourceStatus { export type SourceIndexStatus = 'missing' | 'empty' | 'available'; export interface InfraSourceStatusAdapter { - getIndexNames(requestContext: RequestHandlerContext, aliasName: string): Promise; - hasAlias(requestContext: RequestHandlerContext, aliasName: string): Promise; + getIndexNames( + requestContext: InfraPluginRequestHandlerContext, + aliasName: string + ): Promise; + hasAlias(requestContext: InfraPluginRequestHandlerContext, aliasName: string): Promise; getIndexStatus( - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, indexNames: string ): Promise; } diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 693e98521ada2..207c2efa0f13f 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -28,7 +28,7 @@ import { InfraBackendLibs, InfraDomainLibs } from './lib/infra_types'; import { infraSourceConfigurationSavedObjectType, InfraSources } from './lib/sources'; import { InfraSourceStatus } from './lib/source_status'; import { LogEntriesService } from './services/log_entries'; -import { InfraRequestHandlerContext } from './types'; +import { InfraPluginRequestHandlerContext } from './types'; import { UsageCollector } from './usage/usage_collector'; export const config = { @@ -146,9 +146,9 @@ export class InfraServerPlugin { initInfraServer(this.libs); registerAlertTypes(plugins.alerts, this.libs); - core.http.registerRouteHandlerContext( + core.http.registerRouteHandlerContext( 'infra', - (context, request): InfraRequestHandlerContext => { + (context, request) => { const mlSystem = plugins.ml?.mlSystemProvider(request, context.core.savedObjects.client); const mlAnomalyDetectors = plugins.ml?.anomalyDetectorsProvider( request, diff --git a/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts b/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts index af9e9c5f57c5b..2be812043a0c0 100644 --- a/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts +++ b/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'kibana/server'; import { InventoryCloudAccount } from '../../../../common/http_api/inventory_meta_api'; import { InfraMetadataAggregationResponse, @@ -14,6 +13,7 @@ import { InfraSourceConfiguration } from '../../../lib/sources'; import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InventoryItemType } from '../../../../common/inventory_models/types'; import { findInventoryModel } from '../../../../common/inventory_models'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; export interface CloudMetaData { accounts: InventoryCloudAccount[]; @@ -23,7 +23,7 @@ export interface CloudMetaData { export const getCloudMetadata = async ( framework: KibanaFramework, - req: RequestHandlerContext, + req: InfraPluginRequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, nodeType: InventoryItemType, currentTime: number diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts index 82427a833a20c..57d8fb9eb9509 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; import { InfraMetadataAggregationBucket, InfraMetadataAggregationResponse, @@ -19,7 +19,7 @@ export interface InfraCloudMetricsAdapterResponse { export const getCloudMetricsMetadata = async ( framework: KibanaFramework, - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, instanceId: string, timeRange: { from: number; to: number } diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts index 7753d3161039b..d1dec098af118 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; import { InfraMetadataAggregationBucket, InfraMetadataAggregationResponse, @@ -23,7 +23,7 @@ export interface InfraMetricsAdapterResponse { export const getMetricMetadata = async ( framework: KibanaFramework, - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, nodeId: string, nodeType: InventoryItemType, diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts index b378b42e2ff59..21ef61a55e35f 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts @@ -6,7 +6,7 @@ import { set } from '@elastic/safer-lodash-set'; import { first, startsWith } from 'lodash'; -import { RequestHandlerContext } from 'src/core/server'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InfraSourceConfiguration } from '../../../lib/sources'; import { InfraMetadataInfo } from '../../../../common/http_api/metadata_api'; @@ -17,7 +17,7 @@ import { InventoryItemType } from '../../../../common/inventory_models/types'; export const getNodeInfo = async ( framework: KibanaFramework, - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, nodeId: string, nodeType: InventoryItemType, diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts index b4656178d395e..e52976519ef48 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts @@ -5,14 +5,14 @@ */ import { first, get } from 'lodash'; -import { RequestHandlerContext } from 'src/core/server'; import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InfraSourceConfiguration } from '../../../lib/sources'; import { findInventoryFields } from '../../../../common/inventory_models'; +import type { InfraPluginRequestHandlerContext } from '../../../types'; export const getPodNodeName = async ( framework: KibanaFramework, - requestContext: RequestHandlerContext, + requestContext: InfraPluginRequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, nodeId: string, nodeType: 'host' | 'pod' | 'container', diff --git a/x-pack/plugins/infra/server/types.ts b/x-pack/plugins/infra/server/types.ts index 735569a790f64..2a30bf7cf093d 100644 --- a/x-pack/plugins/infra/server/types.ts +++ b/x-pack/plugins/infra/server/types.ts @@ -3,7 +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 type { RequestHandlerContext } from 'src/core/server'; +import type { DataApiRequestHandlerContext } from '../../../../src/plugins/data/server'; import { MlPluginSetup } from '../../ml/server'; export type MlSystem = ReturnType; @@ -21,8 +22,10 @@ export interface InfraSpacesRequestHandlerContext { export type InfraRequestHandlerContext = InfraMlRequestHandlerContext & InfraSpacesRequestHandlerContext; -declare module 'src/core/server' { - interface RequestHandlerContext { - infra?: InfraRequestHandlerContext; - } +/** + * @internal + */ +export interface InfraPluginRequestHandlerContext extends RequestHandlerContext { + infra: InfraRequestHandlerContext; + search: DataApiRequestHandlerContext; } diff --git a/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts b/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts index 6d16e045d26d5..19169957f4c50 100644 --- a/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts +++ b/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -// import { RequestHandlerContext } from 'src/core/server'; import { findInventoryModel } from '../../common/inventory_models'; // import { KibanaFramework } from '../lib/adapters/framework/kibana_framework_adapter'; import { InventoryItemType } from '../../common/inventory_models/types'; diff --git a/x-pack/plugins/licensing/server/licensing_route_handler_context.ts b/x-pack/plugins/licensing/server/licensing_route_handler_context.ts index 736a2151a3dbd..0a85d6a8ff890 100644 --- a/x-pack/plugins/licensing/server/licensing_route_handler_context.ts +++ b/x-pack/plugins/licensing/server/licensing_route_handler_context.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IContextProvider, RequestHandler, StartServicesAccessor } from 'src/core/server'; +import type { IContextProvider, StartServicesAccessor } from 'src/core/server'; import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; -import { ILicense } from '../common/types'; -import { LicensingPluginStart } from './types'; +import type { ILicense } from '../common/types'; +import type { LicensingPluginStart, LicensingRequestHandlerContext } from './types'; /** * Create a route handler context for access to Kibana license information. @@ -19,7 +19,7 @@ import { LicensingPluginStart } from './types'; export function createRouteHandlerContext( license$: Observable, getStartServices: StartServicesAccessor<{}, LicensingPluginStart> -): IContextProvider, 'licensing'> { +): IContextProvider { return async function licensingRouteHandlerContext() { const [, , { featureUsage }] = await getStartServices(); const license = await license$.pipe(take(1)).toPromise(); diff --git a/x-pack/plugins/licensing/server/mocks.ts b/x-pack/plugins/licensing/server/mocks.ts index 1a2b543b47df5..cab1823f22ac4 100644 --- a/x-pack/plugins/licensing/server/mocks.ts +++ b/x-pack/plugins/licensing/server/mocks.ts @@ -7,7 +7,7 @@ import { BehaviorSubject } from 'rxjs'; import { LicensingPluginSetup, LicensingPluginStart, - LicensingRequestHandlerContext, + LicensingApiRequestHandlerContext, } from './types'; import { licenseMock } from '../common/licensing.mock'; import { featureUsageMock } from './services/feature_usage_service.mock'; @@ -49,8 +49,8 @@ const createStartMock = (): jest.Mocked => { const createRequestHandlerContextMock = ( ...options: Parameters -): jest.Mocked => { - const mock: jest.Mocked = { +): jest.Mocked => { + const mock: jest.Mocked = { license: licenseMock.createLicense(...options), featureUsage: featureUsageMock.createStart(), }; diff --git a/x-pack/plugins/licensing/server/routes/feature_usage.ts b/x-pack/plugins/licensing/server/routes/feature_usage.ts index fa26d09903dc3..da4853145c338 100644 --- a/x-pack/plugins/licensing/server/routes/feature_usage.ts +++ b/x-pack/plugins/licensing/server/routes/feature_usage.ts @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { IRouter, StartServicesAccessor } from 'src/core/server'; +import { StartServicesAccessor } from 'src/core/server'; import { LicensingPluginStart } from '../types'; +import { LicensingRouter } from '../types'; export function registerFeatureUsageRoute( - router: IRouter, + router: LicensingRouter, getStartServices: StartServicesAccessor<{}, LicensingPluginStart> ) { router.get( diff --git a/x-pack/plugins/licensing/server/routes/index.ts b/x-pack/plugins/licensing/server/routes/index.ts index 16065d8e19adc..3a86653c6cb4a 100644 --- a/x-pack/plugins/licensing/server/routes/index.ts +++ b/x-pack/plugins/licensing/server/routes/index.ts @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter, StartServicesAccessor } from 'src/core/server'; +import { StartServicesAccessor } from 'src/core/server'; import { LicensingPluginStart } from '../types'; import { FeatureUsageServiceSetup } from '../services'; import { registerInfoRoute } from './info'; import { registerFeatureUsageRoute } from './feature_usage'; import { registerNotifyFeatureUsageRoute, registerRegisterFeatureRoute } from './internal'; +import { LicensingRouter } from '../types'; export function registerRoutes( - router: IRouter, + router: LicensingRouter, featureUsageSetup: FeatureUsageServiceSetup, getStartServices: StartServicesAccessor<{}, LicensingPluginStart> ) { diff --git a/x-pack/plugins/licensing/server/routes/info.ts b/x-pack/plugins/licensing/server/routes/info.ts index cad873014e271..c07649bf1b12f 100644 --- a/x-pack/plugins/licensing/server/routes/info.ts +++ b/x-pack/plugins/licensing/server/routes/info.ts @@ -3,9 +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. */ -import { IRouter } from 'src/core/server'; +import { LicensingRouter } from '../types'; -export function registerInfoRoute(router: IRouter) { +export function registerInfoRoute(router: LicensingRouter) { router.get({ path: '/api/licensing/info', validate: false }, (context, request, response) => { return response.ok({ body: context.licensing.license, diff --git a/x-pack/plugins/licensing/server/routes/internal/notify_feature_usage.ts b/x-pack/plugins/licensing/server/routes/internal/notify_feature_usage.ts index ec70472574be3..552126bff17b6 100644 --- a/x-pack/plugins/licensing/server/routes/internal/notify_feature_usage.ts +++ b/x-pack/plugins/licensing/server/routes/internal/notify_feature_usage.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import { LicensingRouter } from '../../types'; -export function registerNotifyFeatureUsageRoute(router: IRouter) { +export function registerNotifyFeatureUsageRoute(router: LicensingRouter) { router.post( { path: '/internal/licensing/feature_usage/notify', diff --git a/x-pack/plugins/licensing/server/routes/internal/register_feature.ts b/x-pack/plugins/licensing/server/routes/internal/register_feature.ts index 418e98fc1b2a8..750dc29ed273e 100644 --- a/x-pack/plugins/licensing/server/routes/internal/register_feature.ts +++ b/x-pack/plugins/licensing/server/routes/internal/register_feature.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; import { LicenseType, LICENSE_TYPE } from '../../../common/types'; import { FeatureUsageServiceSetup } from '../../services'; +import { LicensingRouter } from '../../types'; export function registerRegisterFeatureRoute( - router: IRouter, + router: LicensingRouter, featureUsageSetup: FeatureUsageServiceSetup ) { router.post( diff --git a/x-pack/plugins/licensing/server/types.ts b/x-pack/plugins/licensing/server/types.ts index dd1277429eabd..1c7fc69653682 100644 --- a/x-pack/plugins/licensing/server/types.ts +++ b/x-pack/plugins/licensing/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { Observable } from 'rxjs'; -import { ILegacyClusterClient } from 'src/core/server'; +import type { ILegacyClusterClient, IRouter, RequestHandlerContext } from 'src/core/server'; import { ILicense, LicenseStatus, LicenseType } from '../common/types'; import { FeatureUsageServiceSetup, FeatureUsageServiceStart } from './services'; @@ -44,17 +44,23 @@ export interface RawLicense { * The APIs exposed on the `licensing` key of {@link RequestHandlerContext} for plugins that depend on licensing. * @public */ -export interface LicensingRequestHandlerContext { +export interface LicensingApiRequestHandlerContext { featureUsage: FeatureUsageServiceStart; license: ILicense; } -declare module 'src/core/server' { - interface RequestHandlerContext { - licensing: LicensingRequestHandlerContext; - } +/** + * @internal + */ +export interface LicensingRequestHandlerContext extends RequestHandlerContext { + licensing: LicensingApiRequestHandlerContext; } +/** + * @internal + */ +export type LicensingRouter = IRouter; + /** @public */ export interface LicensingPluginSetup { /** diff --git a/x-pack/plugins/licensing/server/wrap_route_with_license_check.ts b/x-pack/plugins/licensing/server/wrap_route_with_license_check.ts index e0cac8d9db208..188c8dbf27157 100644 --- a/x-pack/plugins/licensing/server/wrap_route_with_license_check.ts +++ b/x-pack/plugins/licensing/server/wrap_route_with_license_check.ts @@ -4,26 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - RequestHandler, - RequestHandlerContext, - KibanaRequest, - RouteMethod, - KibanaResponseFactory, -} from 'src/core/server'; +import { RequestHandler, KibanaRequest, RouteMethod, KibanaResponseFactory } from 'src/core/server'; import { ILicense } from '../common/types'; +import type { LicensingRequestHandlerContext } from './types'; export type CheckLicense = ( license: ILicense ) => { valid: false; message: string } | { valid: true; message: null }; -export function wrapRouteWithLicenseCheck( +export function wrapRouteWithLicenseCheck( checkLicense: CheckLicense, - handler: RequestHandler -): RequestHandler { + handler: RequestHandler +): RequestHandler { return async ( - context: RequestHandlerContext, + context: Context, request: KibanaRequest, response: KibanaResponseFactory ) => { diff --git a/x-pack/plugins/lists/server/index.ts b/x-pack/plugins/lists/server/index.ts index ea27073e3053d..2738736d62a3d 100644 --- a/x-pack/plugins/lists/server/index.ts +++ b/x-pack/plugins/lists/server/index.ts @@ -13,7 +13,7 @@ import { ListPlugin } from './plugin'; export { ListClient } from './services/lists/list_client'; export { CreateExceptionListItemOptions } from './services/exception_lists/exception_list_client_types'; export { ExceptionListClient } from './services/exception_lists/exception_list_client'; -export { ListPluginSetup } from './types'; +export type { ListPluginSetup, ListsApiRequestHandlerContext } from './types'; export const config = { schema: ConfigSchema }; export const plugin = (initializerContext: PluginInitializerContext): ListPlugin => diff --git a/x-pack/plugins/lists/server/plugin.ts b/x-pack/plugins/lists/server/plugin.ts index 670f0fe684cc2..c6d42e5ac4f23 100644 --- a/x-pack/plugins/lists/server/plugin.ts +++ b/x-pack/plugins/lists/server/plugin.ts @@ -19,6 +19,7 @@ import type { ContextProviderReturn, ListPluginSetup, ListsPluginStart, + ListsRequestHandlerContext, PluginsStart, } from './types'; import { createConfig$ } from './create_config'; @@ -44,8 +45,11 @@ export class ListPlugin initSavedObjects(core.savedObjects); - core.http.registerRouteHandlerContext('lists', this.createRouteHandlerContext()); - const router = core.http.createRouter(); + core.http.registerRouteHandlerContext( + 'lists', + this.createRouteHandlerContext() + ); + const router = core.http.createRouter(); initRoutes(router, config); return { diff --git a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts index cce4038ff48d6..86072b18db658 100644 --- a/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_ID, ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -18,7 +17,7 @@ import { import { getExceptionListClient } from './utils/get_exception_list_client'; import { validateExceptionListSize } from './validate'; -export const createEndpointListItemRoute = (router: IRouter): void => { +export const createEndpointListItemRoute = (router: ListsPluginRouter): void => { router.post( { options: { diff --git a/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts b/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts index 91b6a328c8649..51f647d76f403 100644 --- a/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_endpoint_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_URL } from '../../common/constants'; import { buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -22,7 +21,7 @@ import { getExceptionListClient } from './utils/get_exception_list_client'; * object. * @param router The router to use. */ -export const createEndpointListRoute = (router: IRouter): void => { +export const createEndpointListRoute = (router: ListsPluginRouter): void => { router.post( { options: { diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts index afcb0f99c8a35..b0456e13d15df 100644 --- a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -19,7 +18,7 @@ import { getExceptionListClient } from './utils/get_exception_list_client'; import { endpointDisallowedFields } from './endpoint_disallowed_fields'; import { validateEndpointExceptionItemEntries, validateExceptionListSize } from './validate'; -export const createExceptionListItemRoute = (router: IRouter): void => { +export const createExceptionListItemRoute = (router: ListsPluginRouter): void => { router.post( { options: { diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts index fd2ba6340009c..506b70c92357c 100644 --- a/x-pack/plugins/lists/server/routes/create_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getExceptionListClient } from './utils/get_exception_list_client'; -export const createExceptionListRoute = (router: IRouter): void => { +export const createExceptionListRoute = (router: ListsPluginRouter): void => { router.post( { options: { diff --git a/x-pack/plugins/lists/server/routes/create_list_index_route.ts b/x-pack/plugins/lists/server/routes/create_list_index_route.ts index be08093dc7055..bca6f1d085eb0 100644 --- a/x-pack/plugins/lists/server/routes/create_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_index_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; import { LIST_INDEX } from '../../common/constants'; @@ -13,7 +12,7 @@ import { acknowledgeSchema } from '../../common/schemas'; import { getListClient } from '.'; -export const createListIndexRoute = (router: IRouter): void => { +export const createListIndexRoute = (router: ListsPluginRouter): void => { router.post( { options: { diff --git a/x-pack/plugins/lists/server/routes/create_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_list_item_route.ts index bd2828d331d83..50d7b141ddeff 100644 --- a/x-pack/plugins/lists/server/routes/create_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { createListItemSchema, listItemSchema } from '../../common/schemas'; @@ -13,7 +12,7 @@ import { validate } from '../../common/shared_imports'; import { getListClient } from '.'; -export const createListItemRoute = (router: IRouter): void => { +export const createListItemRoute = (router: ListsPluginRouter): void => { router.post( { options: { diff --git a/x-pack/plugins/lists/server/routes/create_list_route.ts b/x-pack/plugins/lists/server/routes/create_list_route.ts index 90f5bf9b2c650..49749bbab9044 100644 --- a/x-pack/plugins/lists/server/routes/create_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -13,7 +12,7 @@ import { CreateListSchemaDecoded, createListSchema, listSchema } from '../../com import { getListClient } from '.'; -export const createListRoute = (router: IRouter): void => { +export const createListRoute = (router: ListsPluginRouter): void => { router.post( { options: { diff --git a/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts index 380fdcf862060..c8367561711f6 100644 --- a/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_endpoint_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils'; -export const deleteEndpointListItemRoute = (router: IRouter): void => { +export const deleteEndpointListItemRoute = (router: ListsPluginRouter): void => { router.delete( { options: { diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts index 07e0fad20c900..fd313f48e1cc6 100644 --- a/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils'; -export const deleteExceptionListItemRoute = (router: IRouter): void => { +export const deleteExceptionListItemRoute = (router: ListsPluginRouter): void => { router.delete( { options: { diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts index 769ce732240b7..ae9078de1af9b 100644 --- a/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getErrorMessageExceptionList, getExceptionListClient } from './utils'; -export const deleteExceptionListRoute = (router: IRouter): void => { +export const deleteExceptionListRoute = (router: ListsPluginRouter): void => { router.delete( { options: { diff --git a/x-pack/plugins/lists/server/routes/delete_list_index_route.ts b/x-pack/plugins/lists/server/routes/delete_list_index_route.ts index aa587273036ae..5e2b4cc2a9413 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_index_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_INDEX } from '../../common/constants'; import { buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -29,7 +28,7 @@ import { getListClient } from '.'; * * And ensuring they're all gone */ -export const deleteListIndexRoute = (router: IRouter): void => { +export const deleteListIndexRoute = (router: ListsPluginRouter): void => { router.delete( { options: { diff --git a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts index fa1adf8a39ed8..673520ec59e30 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -13,7 +12,7 @@ import { deleteListItemSchema, listItemArraySchema, listItemSchema } from '../.. import { getListClient } from '.'; -export const deleteListItemRoute = (router: IRouter): void => { +export const deleteListItemRoute = (router: ListsPluginRouter): void => { router.delete( { options: { diff --git a/x-pack/plugins/lists/server/routes/delete_list_route.ts b/x-pack/plugins/lists/server/routes/delete_list_route.ts index bf8fac7f3dd8c..bf411247b74d5 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -22,7 +21,7 @@ import { ExceptionListClient } from '../services/exception_lists/exception_list_ import { getExceptionListClient, getListClient } from '.'; -export const deleteListRoute = (router: IRouter): void => { +export const deleteListRoute = (router: ListsPluginRouter): void => { router.delete( { options: { diff --git a/x-pack/plugins/lists/server/routes/export_exception_list_route.ts b/x-pack/plugins/lists/server/routes/export_exception_list_route.ts index 1394bf48cd2c7..132df176834bb 100644 --- a/x-pack/plugins/lists/server/routes/export_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/export_exception_list_route.ts @@ -4,15 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { exportExceptionListQuerySchema } from '../../common/schemas'; import { getExceptionListClient } from './utils'; -export const exportExceptionListRoute = (router: IRouter): void => { +export const exportExceptionListRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/export_list_item_route.ts b/x-pack/plugins/lists/server/routes/export_list_item_route.ts index 98167931c4346..6f5321380aa77 100644 --- a/x-pack/plugins/lists/server/routes/export_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/export_list_item_route.ts @@ -6,15 +6,14 @@ import { Stream } from 'stream'; -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { exportListItemQuerySchema } from '../../common/schemas'; import { getListClient } from '.'; -export const exportListItemRoute = (router: IRouter): void => { +export const exportListItemRoute = (router: ListsPluginRouter): void => { router.post( { options: { diff --git a/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts index d6a459b3ac961..2b7cb6789b376 100644 --- a/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_endpoint_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_ID, ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getExceptionListClient } from './utils'; -export const findEndpointListItemRoute = (router: IRouter): void => { +export const findEndpointListItemRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts index 103cba700013f..108dd88adb6b3 100644 --- a/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getExceptionListClient } from './utils'; -export const findExceptionListItemRoute = (router: IRouter): void => { +export const findExceptionListItemRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts index 41342261ef681..1a284b27da62e 100644 --- a/x-pack/plugins/lists/server/routes/find_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getExceptionListClient } from './utils'; -export const findExceptionListRoute = (router: IRouter): void => { +export const findExceptionListRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/find_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_list_item_route.ts index 454ea891857c3..4734168d270e6 100644 --- a/x-pack/plugins/lists/server/routes/find_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -18,7 +17,7 @@ import { decodeCursor } from '../services/utils'; import { getListClient } from './utils'; -export const findListItemRoute = (router: IRouter): void => { +export const findListItemRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/find_list_route.ts b/x-pack/plugins/lists/server/routes/find_list_route.ts index d751214006dcc..fc7a69f6df116 100644 --- a/x-pack/plugins/lists/server/routes/find_list_route.ts +++ b/x-pack/plugins/lists/server/routes/find_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -14,7 +13,7 @@ import { decodeCursor } from '../services/utils'; import { getListClient } from './utils'; -export const findListRoute = (router: IRouter): void => { +export const findListRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/import_list_item_route.ts index f7ecc7ac1ac83..0039f7f5d9f90 100644 --- a/x-pack/plugins/lists/server/routes/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/import_list_item_route.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; +import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +17,7 @@ import { createStreamFromBuffer } from './utils/create_stream_from_buffer'; import { getListClient } from '.'; -export const importListItemRoute = (router: IRouter, config: ConfigType): void => { +export const importListItemRoute = (router: ListsPluginRouter, config: ConfigType): void => { router.post( { options: { diff --git a/x-pack/plugins/lists/server/routes/init_routes.ts b/x-pack/plugins/lists/server/routes/init_routes.ts index 1f29d0aaeeb48..a176bd39bb503 100644 --- a/x-pack/plugins/lists/server/routes/init_routes.ts +++ b/x-pack/plugins/lists/server/routes/init_routes.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { ConfigType } from '../config'; import { @@ -46,7 +45,7 @@ import { updateListRoute, } from '.'; -export const initRoutes = (router: IRouter, config: ConfigType): void => { +export const initRoutes = (router: ListsPluginRouter, config: ConfigType): void => { // lists createListRoute(router); readListRoute(router); diff --git a/x-pack/plugins/lists/server/routes/patch_list_item_route.ts b/x-pack/plugins/lists/server/routes/patch_list_item_route.ts index 58cca0313006d..a14d70106cb7b 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -13,7 +12,7 @@ import { listItemSchema, patchListItemSchema } from '../../common/schemas'; import { getListClient } from '.'; -export const patchListItemRoute = (router: IRouter): void => { +export const patchListItemRoute = (router: ListsPluginRouter): void => { router.patch( { options: { diff --git a/x-pack/plugins/lists/server/routes/patch_list_route.ts b/x-pack/plugins/lists/server/routes/patch_list_route.ts index 763f3f495ca17..9c5b59cefb5bc 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -13,7 +12,7 @@ import { listSchema, patchListSchema } from '../../common/schemas'; import { getListClient } from '.'; -export const patchListRoute = (router: IRouter): void => { +export const patchListRoute = (router: ListsPluginRouter): void => { router.patch( { options: { diff --git a/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts index e80347d97bb7a..2dc79483b4348 100644 --- a/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_endpoint_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils'; -export const readEndpointListItemRoute = (router: IRouter): void => { +export const readEndpointListItemRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts index 0cfac6467f089..139a485ab0c5d 100644 --- a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils'; -export const readExceptionListItemRoute = (router: IRouter): void => { +export const readExceptionListItemRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts index d9359881616f4..a8dffa696ea7f 100644 --- a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getErrorMessageExceptionList, getExceptionListClient } from './utils'; -export const readExceptionListRoute = (router: IRouter): void => { +export const readExceptionListRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/read_list_index_route.ts b/x-pack/plugins/lists/server/routes/read_list_index_route.ts index 5524c1beeaa52..1cb6a63e08cf7 100644 --- a/x-pack/plugins/lists/server/routes/read_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_index_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_INDEX } from '../../common/constants'; import { buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -13,7 +12,7 @@ import { listItemIndexExistSchema } from '../../common/schemas'; import { getListClient } from '.'; -export const readListIndexRoute = (router: IRouter): void => { +export const readListIndexRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/read_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_list_item_route.ts index 99d34d0fd84a6..677bc49c21a5e 100644 --- a/x-pack/plugins/lists/server/routes/read_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { listItemArraySchema, listItemSchema, readListItemSchema } from '../../common/schemas'; @@ -13,7 +12,7 @@ import { validate } from '../../common/shared_imports'; import { getListClient } from '.'; -export const readListItemRoute = (router: IRouter): void => { +export const readListItemRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/read_list_route.ts b/x-pack/plugins/lists/server/routes/read_list_route.ts index da3cf73b56819..1854647460f93 100644 --- a/x-pack/plugins/lists/server/routes/read_list_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -13,7 +12,7 @@ import { listSchema, readListSchema } from '../../common/schemas'; import { getListClient } from '.'; -export const readListRoute = (router: IRouter): void => { +export const readListRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/read_privileges_route.ts b/x-pack/plugins/lists/server/routes/read_privileges_route.ts index 4a82f4c5e9cb2..abb43ba43a186 100644 --- a/x-pack/plugins/lists/server/routes/read_privileges_route.ts +++ b/x-pack/plugins/lists/server/routes/read_privileges_route.ts @@ -4,15 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; import { merge } from 'lodash/fp'; +import type { ListsPluginRouter } from '../types'; import { LIST_PRIVILEGES_URL } from '../../common/constants'; import { buildSiemResponse, readPrivileges, transformError } from '../siem_server_deps'; import { getListClient } from './utils'; -export const readPrivilegesRoute = (router: IRouter): void => { +export const readPrivilegesRoute = (router: ListsPluginRouter): void => { router.get( { options: { diff --git a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts index 8312f2fc87b98..fb2149b3c3d66 100644 --- a/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_endpoint_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { ENDPOINT_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getExceptionListClient } from '.'; -export const updateEndpointListItemRoute = (router: IRouter): void => { +export const updateEndpointListItemRoute = (router: ListsPluginRouter): void => { router.put( { options: { diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts index 9ad563724b860..dea53f99810bf 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -18,7 +17,7 @@ import { updateExceptionListItemValidate } from '../../common/schemas/request/up import { getExceptionListClient } from '.'; -export const updateExceptionListItemRoute = (router: IRouter): void => { +export const updateExceptionListItemRoute = (router: ListsPluginRouter): void => { router.put( { options: { diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts index 47008e3b78fae..c3fa907c706d5 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { EXCEPTION_LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -17,7 +16,7 @@ import { import { getErrorMessageExceptionList, getExceptionListClient } from './utils'; -export const updateExceptionListRoute = (router: IRouter): void => { +export const updateExceptionListRoute = (router: ListsPluginRouter): void => { router.put( { options: { diff --git a/x-pack/plugins/lists/server/routes/update_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_list_item_route.ts index 3490027b12747..d7aec97aace67 100644 --- a/x-pack/plugins/lists/server/routes/update_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_item_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_ITEM_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -13,7 +12,7 @@ import { listItemSchema, updateListItemSchema } from '../../common/schemas'; import { getListClient } from '.'; -export const updateListItemRoute = (router: IRouter): void => { +export const updateListItemRoute = (router: ListsPluginRouter): void => { router.put( { options: { diff --git a/x-pack/plugins/lists/server/routes/update_list_route.ts b/x-pack/plugins/lists/server/routes/update_list_route.ts index 8d7d08be4130b..de8412bc3fddb 100644 --- a/x-pack/plugins/lists/server/routes/update_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_route.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; - +import type { ListsPluginRouter } from '../types'; import { LIST_URL } from '../../common/constants'; import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { validate } from '../../common/shared_imports'; @@ -13,7 +12,7 @@ import { listSchema, updateListSchema } from '../../common/schemas'; import { getListClient } from '.'; -export const updateListRoute = (router: IRouter): void => { +export const updateListRoute = (router: ListsPluginRouter): void => { router.put( { options: { diff --git a/x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts b/x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts index ba01ca617fb8b..b9b7beab65295 100644 --- a/x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts +++ b/x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'kibana/server'; - +import type { ListsRequestHandlerContext } from '../../types'; import { ErrorWithStatusCode } from '../../error_with_status_code'; import { ExceptionListClient } from '../../services/exception_lists/exception_list_client'; -export const getExceptionListClient = (context: RequestHandlerContext): ExceptionListClient => { +export const getExceptionListClient = ( + context: ListsRequestHandlerContext +): ExceptionListClient => { const exceptionLists = context.lists?.getExceptionListClient(); if (exceptionLists == null) { throw new ErrorWithStatusCode('Exception lists is not found as a plugin', 404); diff --git a/x-pack/plugins/lists/server/routes/utils/get_list_client.ts b/x-pack/plugins/lists/server/routes/utils/get_list_client.ts index 6ad69fd994bfd..bc06d753b7b66 100644 --- a/x-pack/plugins/lists/server/routes/utils/get_list_client.ts +++ b/x-pack/plugins/lists/server/routes/utils/get_list_client.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'kibana/server'; - import { ListClient } from '../../services/lists/list_client'; import { ErrorWithStatusCode } from '../../error_with_status_code'; +import type { ListsRequestHandlerContext } from '../../types'; -export const getListClient = (context: RequestHandlerContext): ListClient => { +export const getListClient = (context: ListsRequestHandlerContext): ListClient => { const lists = context.lists?.getListClient(); if (lists == null) { throw new ErrorWithStatusCode('Lists is not found as a plugin', 404); diff --git a/x-pack/plugins/lists/server/types.ts b/x-pack/plugins/lists/server/types.ts index 7d0a24ccddfd0..70cad625245b0 100644 --- a/x-pack/plugins/lists/server/types.ts +++ b/x-pack/plugins/lists/server/types.ts @@ -6,8 +6,9 @@ import { IContextProvider, + IRouter, LegacyAPICaller, - RequestHandler, + RequestHandlerContext, SavedObjectsClientContract, } from 'kibana/server'; @@ -17,7 +18,7 @@ import type { SpacesPluginStart } from '../../spaces/server'; import { ListClient } from './services/lists/list_client'; import { ExceptionListClient } from './services/exception_lists/exception_list_client'; -export type ContextProvider = IContextProvider, 'lists'>; +export type ContextProvider = IContextProvider; export type ListsPluginStart = void; export interface PluginsStart { security: SecurityPluginStart | undefined | null; @@ -40,15 +41,26 @@ export interface ListPluginSetup { getListClient: GetListClientType; } -export type ContextProviderReturn = Promise<{ +/** + * @public + */ +export interface ListsApiRequestHandlerContext { getListClient: () => ListClient; getExceptionListClient: () => ExceptionListClient; -}>; -declare module 'src/core/server' { - interface RequestHandlerContext { - lists?: { - getExceptionListClient: () => ExceptionListClient; - getListClient: () => ListClient; - }; - } } + +/** + * @internal + */ +export interface ListsRequestHandlerContext extends RequestHandlerContext { + lists?: ListsApiRequestHandlerContext; +} + +/** + * @internal + */ +export type ListsPluginRouter = IRouter; +/** + * @internal + */ +export type ContextProviderReturn = Promise; diff --git a/x-pack/plugins/logstash/server/plugin.ts b/x-pack/plugins/logstash/server/plugin.ts index 4a6d476551db0..2c0a714b96910 100644 --- a/x-pack/plugins/logstash/server/plugin.ts +++ b/x-pack/plugins/logstash/server/plugin.ts @@ -16,6 +16,7 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../features/serve import { SecurityPluginSetup } from '../../security/server'; import { registerRoutes } from './routes'; +import type { LogstashRequestHandlerContext } from './types'; interface SetupDeps { licensing: LicensingPluginSetup; @@ -55,9 +56,12 @@ export class LogstashPlugin implements Plugin { start(core: CoreStart) { const esClient = core.elasticsearch.legacy.createClient('logstash'); - this.coreSetup!.http.registerRouteHandlerContext('logstash', async (context, request) => { - return { esClient: esClient.asScoped(request) }; - }); + this.coreSetup!.http.registerRouteHandlerContext( + 'logstash', + async (context, request) => { + return { esClient: esClient.asScoped(request) }; + } + ); } stop() { if (this.esClient) { diff --git a/x-pack/plugins/logstash/server/routes/cluster/load.ts b/x-pack/plugins/logstash/server/routes/cluster/load.ts index 18fe21f3da675..307e1ebdb55a5 100644 --- a/x-pack/plugins/logstash/server/routes/cluster/load.ts +++ b/x-pack/plugins/logstash/server/routes/cluster/load.ts @@ -3,12 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; import { Cluster } from '../../models/cluster'; import { checkLicense } from '../../lib/check_license'; +import type { LogstashPluginRouter } from '../../types'; -export function registerClusterLoadRoute(router: IRouter) { +export function registerClusterLoadRoute(router: LogstashPluginRouter) { router.get( { path: '/api/logstash/cluster', diff --git a/x-pack/plugins/logstash/server/routes/index.ts b/x-pack/plugins/logstash/server/routes/index.ts index 422afbf7d411e..b62be3a566d4f 100644 --- a/x-pack/plugins/logstash/server/routes/index.ts +++ b/x-pack/plugins/logstash/server/routes/index.ts @@ -3,8 +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 { IRouter } from 'src/core/server'; import { SecurityPluginSetup } from '../../../security/server'; +import type { LogstashPluginRouter } from '../types'; import { registerClusterLoadRoute } from './cluster'; import { registerPipelineDeleteRoute, @@ -13,7 +13,7 @@ import { } from './pipeline'; import { registerPipelinesListRoute, registerPipelinesDeleteRoute } from './pipelines'; -export function registerRoutes(router: IRouter, security?: SecurityPluginSetup) { +export function registerRoutes(router: LogstashPluginRouter, security?: SecurityPluginSetup) { registerClusterLoadRoute(router); registerPipelineDeleteRoute(router); diff --git a/x-pack/plugins/logstash/server/routes/pipeline/delete.ts b/x-pack/plugins/logstash/server/routes/pipeline/delete.ts index d94b3b94b1df0..e6f335c390ed1 100644 --- a/x-pack/plugins/logstash/server/routes/pipeline/delete.ts +++ b/x-pack/plugins/logstash/server/routes/pipeline/delete.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; - +import type { LogstashPluginRouter } from '../../types'; import { checkLicense } from '../../lib/check_license'; -export function registerPipelineDeleteRoute(router: IRouter) { +export function registerPipelineDeleteRoute(router: LogstashPluginRouter) { router.delete( { path: '/api/logstash/pipeline/{id}', diff --git a/x-pack/plugins/logstash/server/routes/pipeline/load.ts b/x-pack/plugins/logstash/server/routes/pipeline/load.ts index 69d16fb82d869..d26e901ca252a 100644 --- a/x-pack/plugins/logstash/server/routes/pipeline/load.ts +++ b/x-pack/plugins/logstash/server/routes/pipeline/load.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { LogstashPluginRouter } from '../../types'; import { Pipeline } from '../../models/pipeline'; import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; import { checkLicense } from '../../lib/check_license'; -export function registerPipelineLoadRoute(router: IRouter) { +export function registerPipelineLoadRoute(router: LogstashPluginRouter) { router.get( { path: '/api/logstash/pipeline/{id}', diff --git a/x-pack/plugins/logstash/server/routes/pipeline/save.ts b/x-pack/plugins/logstash/server/routes/pipeline/save.ts index e5f28bda1974c..ddeb134ca6046 100644 --- a/x-pack/plugins/logstash/server/routes/pipeline/save.ts +++ b/x-pack/plugins/logstash/server/routes/pipeline/save.ts @@ -5,14 +5,17 @@ */ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; -import { IRouter } from 'src/core/server'; import { Pipeline } from '../../models/pipeline'; import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; import { SecurityPluginSetup } from '../../../../security/server'; import { checkLicense } from '../../lib/check_license'; +import type { LogstashPluginRouter } from '../../types'; -export function registerPipelineSaveRoute(router: IRouter, security?: SecurityPluginSetup) { +export function registerPipelineSaveRoute( + router: LogstashPluginRouter, + security?: SecurityPluginSetup +) { router.put( { path: '/api/logstash/pipeline/{id}', diff --git a/x-pack/plugins/logstash/server/routes/pipelines/delete.ts b/x-pack/plugins/logstash/server/routes/pipelines/delete.ts index 4eb3cae1d0956..1dff01f8768b7 100644 --- a/x-pack/plugins/logstash/server/routes/pipelines/delete.ts +++ b/x-pack/plugins/logstash/server/routes/pipelines/delete.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; -import { LegacyAPICaller, IRouter } from 'src/core/server'; +import { LegacyAPICaller } from 'src/core/server'; import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; import { checkLicense } from '../../lib/check_license'; +import type { LogstashPluginRouter } from '../../types'; async function deletePipelines(callWithRequest: LegacyAPICaller, pipelineIds: string[]) { const deletePromises = pipelineIds.map((pipelineId) => { @@ -29,7 +30,7 @@ async function deletePipelines(callWithRequest: LegacyAPICaller, pipelineIds: st }; } -export function registerPipelinesDeleteRoute(router: IRouter) { +export function registerPipelinesDeleteRoute(router: LogstashPluginRouter) { router.post( { path: '/api/logstash/pipelines/delete', @@ -42,7 +43,7 @@ export function registerPipelinesDeleteRoute(router: IRouter) { wrapRouteWithLicenseCheck( checkLicense, router.handleLegacyErrors(async (context, request, response) => { - const client = context.logstash!.esClient; + const client = context.logstash.esClient; const results = await deletePipelines(client.callAsCurrentUser, request.body.pipelineIds); return response.ok({ body: { results } }); diff --git a/x-pack/plugins/logstash/server/routes/pipelines/list.ts b/x-pack/plugins/logstash/server/routes/pipelines/list.ts index 78690d3091cbd..e6e7c40eecaea 100644 --- a/x-pack/plugins/logstash/server/routes/pipelines/list.ts +++ b/x-pack/plugins/logstash/server/routes/pipelines/list.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { LegacyAPICaller, IRouter } from 'src/core/server'; +import { LegacyAPICaller } from 'src/core/server'; +import type { LogstashPluginRouter } from '../../types'; import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; import { PipelineListItem } from '../../models/pipeline_list_item'; @@ -20,7 +21,7 @@ async function fetchPipelines(callWithRequest: LegacyAPICaller) { return await callWithRequest('transport.request', params); } -export function registerPipelinesListRoute(router: IRouter) { +export function registerPipelinesListRoute(router: LogstashPluginRouter) { router.get( { path: '/api/logstash/pipelines', diff --git a/x-pack/plugins/logstash/server/types.ts b/x-pack/plugins/logstash/server/types.ts index d9fd109b20943..1fb74d43f1f4f 100644 --- a/x-pack/plugins/logstash/server/types.ts +++ b/x-pack/plugins/logstash/server/types.ts @@ -3,7 +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 { ILegacyScopedClusterClient } from 'src/core/server'; +import type { ILegacyScopedClusterClient, IRouter, RequestHandlerContext } from 'src/core/server'; +import type { LicensingApiRequestHandlerContext } from '../../licensing/server'; export interface PipelineListItemOptions { id: string; @@ -12,10 +13,17 @@ export interface PipelineListItemOptions { username: string; } -declare module 'src/core/server' { - interface RequestHandlerContext { - logstash?: { - esClient: ILegacyScopedClusterClient; - }; - } +/** + * @internal + */ +export interface LogstashRequestHandlerContext extends RequestHandlerContext { + logstash: { + esClient: ILegacyScopedClusterClient; + }; + licensing: LicensingApiRequestHandlerContext; } + +/** + * @internal + */ +export type LogstashPluginRouter = IRouter; diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index c7b3f13dd6954..a78c50722368d 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -12,7 +12,6 @@ import { TypeOf } from '@kbn/config-schema'; import { Logger, PluginInitializerContext, - RequestHandlerContext, KibanaRequest, KibanaResponseFactory, CoreSetup, @@ -47,6 +46,7 @@ import { PluginsSetup, PluginsStart, LegacyRequest, + RequestHandlerContextMonitoringPlugin, } from './types'; import { Globals } from './static_globals'; @@ -90,7 +90,7 @@ export class Plugin { .pipe(first()) .toPromise(); - const router = core.http.createRouter(); + const router = core.http.createRouter(); this.legacyShimDependencies = { router, instanceUuid: this.initializerContext.env.instanceUuid, @@ -307,7 +307,7 @@ export class Plugin { route: (options: any) => { const method = options.method; const handler = async ( - context: RequestHandlerContext, + context: RequestHandlerContextMonitoringPlugin, req: KibanaRequest, res: KibanaResponseFactory ) => { diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index dd6ec9c7930e5..fbdd01e56c657 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -4,10 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ import { Observable } from 'rxjs'; -import { IRouter, ILegacyClusterClient, Logger, ILegacyCustomClusterClient } from 'kibana/server'; +import type { + IRouter, + ILegacyClusterClient, + Logger, + ILegacyCustomClusterClient, + RequestHandlerContext, +} from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { LicenseFeature, ILicense } from '../../licensing/server'; -import { PluginStartContract as ActionsPluginsStartContact } from '../../actions/server'; +import type { + PluginStartContract as ActionsPluginsStartContact, + ActionsApiRequestHandlerContext, +} from '../../actions/server'; +import type { AlertingApiRequestHandlerContext } from '../../alerts/server'; import { PluginStartContract as AlertingPluginStartContract, PluginSetupContract as AlertingPluginSetupContract, @@ -42,6 +52,11 @@ export interface PluginsSetup { cloud?: CloudSetup; } +export interface RequestHandlerContextMonitoringPlugin extends RequestHandlerContext { + actions?: ActionsApiRequestHandlerContext; + alerting?: AlertingApiRequestHandlerContext; +} + export interface PluginsStart { alerts: AlertingPluginStartContract; actions: ActionsPluginsStartContact; @@ -53,7 +68,7 @@ export interface MonitoringCoreConfig { export interface RouteDependencies { cluster: ILegacyCustomClusterClient; - router: IRouter; + router: IRouter; licenseService: MonitoringLicenseService; encryptedSavedObjects?: EncryptedSavedObjectsPluginSetup; logger: Logger; @@ -66,7 +81,7 @@ export interface MonitoringCore { } export interface LegacyShimDependencies { - router: IRouter; + router: IRouter; instanceUuid: string; esDataClient: ILegacyClusterClient; kibanaStatsCollector: any; diff --git a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts index f57e1a774a8e2..6fcd780d5af29 100644 --- a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts +++ b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts @@ -3,15 +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 { - CoreSetup, - PluginInitializerContext, - KibanaRequest, - RequestHandlerContext, -} from 'kibana/server'; +import { CoreSetup, PluginInitializerContext, KibanaRequest } from 'kibana/server'; import { PromiseReturnType } from '../../../typings/common'; import { createAnnotationsClient } from './create_annotations_client'; import { registerAnnotationAPIs } from './register_annotation_apis'; +import type { ObservabilityRequestHandlerContext } from '../../types'; interface Params { index: string; @@ -36,7 +32,10 @@ export async function bootstrapAnnotations({ index, core, context }: Params) { }); return { - getScopedAnnotationsClient: (requestContext: RequestHandlerContext, request: KibanaRequest) => { + getScopedAnnotationsClient: ( + requestContext: ObservabilityRequestHandlerContext, + request: KibanaRequest + ) => { return createAnnotationsClient({ index, apiCaller: requestContext.core.elasticsearch.legacy.client.callAsCurrentUser, diff --git a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts index 5d0fdc65117bf..8f0b53b5a3df2 100644 --- a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts +++ b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts @@ -15,6 +15,7 @@ import { } from '../../../common/annotations'; import { ScopedAnnotationsClient } from './bootstrap_annotations'; import { createAnnotationsClient } from './create_annotations_client'; +import type { ObservabilityRequestHandlerContext } from '../../types'; const unknowns = schema.object({}, { unknowns: 'allow' }); @@ -30,8 +31,12 @@ export function registerAnnotationAPIs({ function wrapRouteHandler>( types: TType, handler: (params: { data: t.TypeOf; client: ScopedAnnotationsClient }) => Promise - ): RequestHandler { - return async (...args: Parameters) => { + ): RequestHandler { + return async ( + ...args: Parameters< + RequestHandler + > + ) => { const [context, request, response] = args; const rt = types; @@ -79,7 +84,7 @@ export function registerAnnotationAPIs({ }; } - const router = core.http.createRouter(); + const router = core.http.createRouter(); router.post( { diff --git a/x-pack/plugins/observability/server/types.ts b/x-pack/plugins/observability/server/types.ts new file mode 100644 index 0000000000000..ee4761a94dcc5 --- /dev/null +++ b/x-pack/plugins/observability/server/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { LicensingApiRequestHandlerContext } from '../../licensing/server'; + +/** + * @internal + */ +export interface ObservabilityRequestHandlerContext extends RequestHandlerContext { + licensing: LicensingApiRequestHandlerContext; +} + +/** + * @internal + */ +export type ObservabilityPluginRouter = IRouter; diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index 2c1ac95d6e824..b227b3ba5d0d5 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -10,7 +10,6 @@ import { first, map, take } from 'rxjs/operators'; import { BasePath, ElasticsearchServiceSetup, - IRouter, KibanaRequest, SavedObjectsClientContract, SavedObjectsServiceStart, @@ -27,10 +26,11 @@ import { checkLicense, getExportTypesRegistry, LevelLogger } from './lib'; import { ESQueueInstance } from './lib/create_queue'; import { screenshotsObservableFactory, ScreenshotsObservableFn } from './lib/screenshots'; import { ReportingStore } from './lib/store'; +import { ReportingPluginRouter } from './types'; export interface ReportingInternalSetup { basePath: Pick; - router: IRouter; + router: ReportingPluginRouter; features: FeaturesPluginSetup; elasticsearch: ElasticsearchServiceSetup; licensing: LicensingPluginSetup; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts index 96653b1573662..24516210efc54 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/create_job.ts @@ -6,7 +6,6 @@ import { notFound, notImplemented } from '@hapi/boom'; import { get } from 'lodash'; -import { RequestHandlerContext } from 'src/core/server'; import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; import { CsvFromSavedObjectRequest } from '../../routes/generate_from_savedobject_immediate'; import { CreateJobFnFactory } from '../../types'; @@ -18,10 +17,11 @@ import { SavedObjectServiceError, VisObjectAttributesJSON, } from './types'; +import type { ReportingRequestHandlerContext } from '../../types'; export type ImmediateCreateJobFn = ( jobParams: JobParamsPanelCsv, - context: RequestHandlerContext, + context: ReportingRequestHandlerContext, req: CsvFromSavedObjectRequest ) => Promise; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts index 5e95eec99871f..f8df8c8f16a65 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/execute_job.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { KibanaRequest } from 'src/core/server'; import { CancellationToken } from '../../../common'; import { CONTENT_TYPE_CSV, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; import { TaskRunResult } from '../../lib/tasks'; @@ -12,6 +12,7 @@ import { RunTaskFnFactory } from '../../types'; import { createGenerateCsv } from '../csv/generate_csv'; import { getGenerateCsvParams } from './lib/get_csv_job'; import { JobPayloadPanelCsv } from './types'; +import type { ReportingRequestHandlerContext } from '../../types'; /* * ImmediateExecuteFn receives the job doc payload because the payload was @@ -20,7 +21,7 @@ import { JobPayloadPanelCsv } from './types'; export type ImmediateExecuteFn = ( jobId: null, job: JobPayloadPanelCsv, - context: RequestHandlerContext, + context: ReportingRequestHandlerContext, req: KibanaRequest ) => Promise; diff --git a/x-pack/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/plugins/reporting/server/lib/enqueue_job.ts index 305247e6f8637..daa662478d82b 100644 --- a/x-pack/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/plugins/reporting/server/lib/enqueue_job.ts @@ -4,18 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { KibanaRequest } from 'src/core/server'; import { ReportingCore } from '../'; import { durationToNumber } from '../../common/schema_utils'; import { BaseParams, ReportingUser } from '../types'; import { LevelLogger } from './'; import { Report } from './store'; +import type { ReportingRequestHandlerContext } from '../types'; export type EnqueueJobFn = ( exportTypeId: string, jobParams: BaseParams, user: ReportingUser, - context: RequestHandlerContext, + context: ReportingRequestHandlerContext, request: KibanaRequest ) => Promise; @@ -36,7 +37,7 @@ export function enqueueJobFactory( exportTypeId: string, jobParams: BaseParams, user: ReportingUser, - context: RequestHandlerContext, + context: ReportingRequestHandlerContext, request: KibanaRequest ) { const exportType = reporting.getExportTypesRegistry().getById(exportTypeId); diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index 6a93a35bfcc84..05556f050e213 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -16,15 +16,10 @@ import { registerRoutes } from './routes'; import { setFieldFormats } from './services'; import { ReportingSetup, ReportingSetupDeps, ReportingStart, ReportingStartDeps } from './types'; import { registerReportingUsageCollector } from './usage'; +import type { ReportingRequestHandlerContext } from './types'; const kbToBase64Length = (kb: number) => Math.floor((kb * 1024 * 8) / 6); -declare module 'src/core/server' { - interface RequestHandlerContext { - reporting?: ReportingStart | null; - } -} - export class ReportingPlugin implements Plugin { private readonly initializerContext: PluginInitializerContext; @@ -39,6 +34,7 @@ export class ReportingPlugin public setup(core: CoreSetup, plugins: ReportingSetupDeps) { // prevent throwing errors in route handlers about async deps not being initialized + // @ts-expect-error null is not assignable to object. use a boolean property to ensure reporting API is enabled. core.http.registerRouteHandlerContext(PLUGIN_ID, () => { if (this.reportingCore.pluginIsStarted()) { return {}; // ReportingStart contract @@ -73,7 +69,7 @@ export class ReportingPlugin const { features, licensing, security, spaces } = plugins; const { initializerContext: initContext, reportingCore } = this; - const router = http.createRouter(); + const router = http.createRouter(); const basePath = http.basePath; reportingCore.pluginSetup({ diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts index 71ca0661a42a9..65519dab9a975 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts @@ -12,6 +12,7 @@ import supertest from 'supertest'; import { ReportingCore } from '../..'; import { createMockLevelLogger, createMockReportingCore } from '../../test_helpers'; import { registerDiagnoseBrowser } from './browser'; +import type { ReportingRequestHandlerContext } from '../../types'; jest.mock('child_process'); jest.mock('readline'); @@ -47,7 +48,11 @@ describe('POST /diagnose/browser', () => { beforeEach(async () => { ({ server, httpSetup } = await setupServer(reportingSymbol)); - httpSetup.registerRouteHandlerContext(reportingSymbol, 'reporting', () => ({})); + httpSetup.registerRouteHandlerContext( + reportingSymbol, + 'reporting', + () => ({}) + ); const mockSetupDeps = ({ elasticsearch: { diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/config.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/config.test.ts index a112d04f38c7b..ef84ee068e8a3 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/config.test.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/config.test.ts @@ -10,6 +10,7 @@ import supertest from 'supertest'; import { ReportingCore } from '../..'; import { createMockReportingCore, createMockLevelLogger } from '../../test_helpers'; import { registerDiagnoseConfig } from './config'; +import type { ReportingRequestHandlerContext } from '../../types'; type SetupServerReturn = UnwrapPromise>; @@ -25,7 +26,11 @@ describe('POST /diagnose/config', () => { beforeEach(async () => { ({ server, httpSetup } = await setupServer(reportingSymbol)); - httpSetup.registerRouteHandlerContext(reportingSymbol, 'reporting', () => ({})); + httpSetup.registerRouteHandlerContext( + reportingSymbol, + 'reporting', + () => ({}) + ); mockSetupDeps = ({ elasticsearch: { diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts index 287da0d2ed5ec..37a8412e4269c 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts @@ -10,6 +10,7 @@ import supertest from 'supertest'; import { ReportingCore } from '../..'; import { createMockReportingCore, createMockLevelLogger } from '../../test_helpers'; import { registerDiagnoseScreenshot } from './screenshot'; +import type { ReportingRequestHandlerContext } from '../../types'; jest.mock('../../export_types/png/lib/generate_png'); @@ -44,7 +45,11 @@ describe('POST /diagnose/screenshot', () => { beforeEach(async () => { ({ server, httpSetup } = await setupServer(reportingSymbol)); - httpSetup.registerRouteHandlerContext(reportingSymbol, 'reporting', () => ({})); + httpSetup.registerRouteHandlerContext( + reportingSymbol, + 'reporting', + () => ({}) + ); const mockSetupDeps = ({ elasticsearch: { diff --git a/x-pack/plugins/reporting/server/routes/generation.test.ts b/x-pack/plugins/reporting/server/routes/generation.test.ts index 867af75c8de27..b904ff92be3ec 100644 --- a/x-pack/plugins/reporting/server/routes/generation.test.ts +++ b/x-pack/plugins/reporting/server/routes/generation.test.ts @@ -13,6 +13,7 @@ import { ReportingCore } from '..'; import { ExportTypesRegistry } from '../lib/export_types_registry'; import { createMockReportingCore, createMockLevelLogger } from '../test_helpers'; import { registerJobGenerationRoutes } from './generation'; +import type { ReportingRequestHandlerContext } from '../types'; type SetupServerReturn = UnwrapPromise>; @@ -46,7 +47,11 @@ describe('POST /api/reporting/generate', () => { beforeEach(async () => { ({ server, httpSetup } = await setupServer(reportingSymbol)); - httpSetup.registerRouteHandlerContext(reportingSymbol, 'reporting', () => ({})); + httpSetup.registerRouteHandlerContext( + reportingSymbol, + 'reporting', + () => ({}) + ); callClusterStub = sinon.stub().resolves({}); diff --git a/x-pack/plugins/reporting/server/routes/jobs.test.ts b/x-pack/plugins/reporting/server/routes/jobs.test.ts index fc1cfd00493c3..ccbec158cb5a1 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.test.ts @@ -12,7 +12,7 @@ import { ReportingCore } from '..'; import { ReportingInternalSetup } from '../core'; import { ExportTypesRegistry } from '../lib/export_types_registry'; import { createMockConfig, createMockConfigSchema, createMockReportingCore } from '../test_helpers'; -import { ExportTypeDefinition } from '../types'; +import { ExportTypeDefinition, ReportingRequestHandlerContext } from '../types'; import { registerJobInfoRoutes } from './jobs'; type SetupServerReturn = UnwrapPromise>; @@ -35,7 +35,11 @@ describe('GET /api/reporting/jobs/download', () => { beforeEach(async () => { ({ server, httpSetup } = await setupServer(reportingSymbol)); - httpSetup.registerRouteHandlerContext(reportingSymbol, 'reporting', () => ({})); + httpSetup.registerRouteHandlerContext( + reportingSymbol, + 'reporting', + () => ({}) + ); core = await createMockReportingCore(config, ({ elasticsearch: { legacy: { client: { callAsInternalUser: jest.fn() } }, diff --git a/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts index cce002a0e6935..18191a9f1b718 100644 --- a/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts +++ b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'kibana/server'; +import { KibanaRequest, KibanaResponseFactory } from 'kibana/server'; import { coreMock, httpServerMock } from 'src/core/server/mocks'; import { ReportingCore } from '../../'; import { ReportingInternalSetup } from '../../core'; @@ -14,6 +14,7 @@ import { createMockReportingCore, } from '../../test_helpers'; import { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing'; +import type { ReportingRequestHandlerContext } from '../../types'; let mockCore: ReportingCore; const mockConfig: any = { 'server.basePath': '/sbp', 'roles.allow': ['reporting_user'] }; @@ -23,7 +24,7 @@ const mockReportingConfig = createMockConfig(mockReportingConfigSchema); const getMockContext = () => (({ core: coreMock.createRequestHandlerContext(), - } as unknown) as RequestHandlerContext); + } as unknown) as ReportingRequestHandlerContext); const getMockRequest = () => ({ diff --git a/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index 4d57bc154444e..e8be2b1c92882 100644 --- a/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts +++ b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -8,11 +8,17 @@ import { RequestHandler, RouteMethod } from 'src/core/server'; import { AuthenticatedUser } from '../../../../security/server'; import { ReportingCore } from '../../core'; import { getUserFactory } from './get_user'; +import type { ReportingRequestHandlerContext } from '../../types'; const superuserRole = 'superuser'; type ReportingRequestUser = AuthenticatedUser | false; -export type RequestHandlerUser = RequestHandler extends (...a: infer U) => infer R +export type RequestHandlerUser = RequestHandler< + P, + Q, + B, + ReportingRequestHandlerContext +> extends (...a: infer U) => infer R ? (user: ReportingRequestUser, ...a: U) => R : never; @@ -21,7 +27,9 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting ) { const setupDeps = reporting.getPluginSetupDeps(); const getUser = getUserFactory(setupDeps.security); - return (handler: RequestHandlerUser): RequestHandler => { + return ( + handler: RequestHandlerUser + ): RequestHandler => { return (context, req, res) => { let user: ReportingRequestUser = false; if (setupDeps.security && setupDeps.security.license.isEnabled()) { diff --git a/x-pack/plugins/reporting/server/routes/types.d.ts b/x-pack/plugins/reporting/server/routes/types.d.ts index b3f9225c3dce5..de451773437fc 100644 --- a/x-pack/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/plugins/reporting/server/routes/types.d.ts @@ -4,14 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; -import { BaseParams, BasePayload, ReportingUser } from '../types'; +import { KibanaRequest, KibanaResponseFactory } from 'src/core/server'; +import type { + BaseParams, + BasePayload, + ReportingUser, + ReportingRequestHandlerContext, +} from '../types'; export type HandlerFunction = ( user: ReportingUser, exportType: string, jobParams: BaseParams, - context: RequestHandlerContext, + context: ReportingRequestHandlerContext, req: KibanaRequest, res: KibanaResponseFactory ) => any; diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts index 8cd26df032f64..8ad458ed65d4e 100644 --- a/x-pack/plugins/reporting/server/types.ts +++ b/x-pack/plugins/reporting/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import type { IRouter, KibanaRequest, RequestHandlerContext } from 'src/core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { DataPluginStart } from 'src/plugins/data/server/plugin'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; @@ -58,7 +58,7 @@ export interface BasePayload extends BaseParams { // default fn type for CreateJobFnFactory export type CreateJobFn = ( jobParams: JobParamsType, - context: RequestHandlerContext, + context: ReportingRequestHandlerContext, request: KibanaRequest ) => Promise; @@ -89,3 +89,15 @@ export interface ExportTypeDefinition; validLicenses: string[]; } + +/** + * @internal + */ +export interface ReportingRequestHandlerContext extends RequestHandlerContext { + reporting: ReportingStart | null; +} + +/** + * @internal + */ +export type ReportingPluginRouter = IRouter; diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index 3c670f56c7d8f..dc505d1aa5850 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -4,12 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -declare module 'src/core/server' { - interface RequestHandlerContext { - rollup?: RollupContext; - } -} - import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; import { @@ -18,14 +12,13 @@ import { Plugin, Logger, PluginInitializerContext, - ILegacyScopedClusterClient, SharedGlobalConfig, } from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import { PLUGIN, CONFIG_ROLLUPS } from '../common'; -import { Dependencies } from './types'; +import { Dependencies, RollupHandlerContext } from './types'; import { registerApiRoutes } from './routes'; import { License } from './services'; import { registerRollupUsageCollector } from './collectors'; @@ -36,9 +29,6 @@ import { isEsError } from './shared_imports'; import { formatEsError } from './lib/format_es_error'; import { getCapabilitiesForRollupIndices } from '../../../../src/plugins/data/server'; -interface RollupContext { - client: ILegacyScopedClusterClient; -} async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { const [core] = await getStartServices(); // Extend the elasticsearchJs client with additional endpoints. @@ -91,12 +81,15 @@ export class RollupPlugin implements Plugin { ], }); - http.registerRouteHandlerContext('rollup', async (context, request) => { - this.rollupEsClient = this.rollupEsClient ?? (await getCustomEsClient(getStartServices)); - return { - client: this.rollupEsClient.asScoped(request), - }; - }); + http.registerRouteHandlerContext( + 'rollup', + async (context, request) => { + this.rollupEsClient = this.rollupEsClient ?? (await getCustomEsClient(getStartServices)); + return { + client: this.rollupEsClient.asScoped(request), + }; + } + ); registerApiRoutes({ router: http.createRouter(), diff --git a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts index c5c56336def1a..cdc4c255a5aba 100644 --- a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts @@ -28,7 +28,7 @@ export const registerSearchRoute = ({ license.guardApiRoute(async (context, request, response) => { try { const requests = request.body.map(({ index, query }: { index: string; query?: any }) => - context.rollup!.client.callAsCurrentUser('rollup.search', { + context.rollup.client.callAsCurrentUser('rollup.search', { index, rest_total_hits_as_int: true, body: query, diff --git a/x-pack/plugins/rollup/server/services/license.ts b/x-pack/plugins/rollup/server/services/license.ts index 5424092a01ee5..f80f91b8d03ee 100644 --- a/x-pack/plugins/rollup/server/services/license.ts +++ b/x-pack/plugins/rollup/server/services/license.ts @@ -4,15 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { Logger } from 'src/core/server'; -import { - KibanaRequest, - KibanaResponseFactory, - RequestHandler, - RequestHandlerContext, -} from 'src/core/server'; +import { KibanaRequest, KibanaResponseFactory, RequestHandler } from 'src/core/server'; import { LicensingPluginSetup } from '../../../licensing/server'; import { LicenseType } from '../../../licensing/common/types'; +import type { RollupHandlerContext } from '../types'; export interface LicenseStatus { isValid: boolean; @@ -59,11 +55,11 @@ export class License { }); } - guardApiRoute(handler: RequestHandler) { + guardApiRoute(handler: RequestHandler) { const license = this; return function licenseCheck( - ctx: RequestHandlerContext, + ctx: RollupHandlerContext, request: KibanaRequest, response: KibanaResponseFactory ) { diff --git a/x-pack/plugins/rollup/server/types.ts b/x-pack/plugins/rollup/server/types.ts index 89e13e69c4da2..e6fe0eae15edd 100644 --- a/x-pack/plugins/rollup/server/types.ts +++ b/x-pack/plugins/rollup/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import { IRouter, ILegacyScopedClusterClient, RequestHandlerContext } from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; @@ -26,7 +26,7 @@ export interface Dependencies { } export interface RouteDependencies { - router: IRouter; + router: RollupPluginRouter; license: License; lib: { isEsError: typeof isEsError; @@ -37,3 +37,22 @@ export interface RouteDependencies { IndexPatternsFetcher: typeof IndexPatternsFetcher; }; } + +/** + * @internal + */ +interface RollupApiRequestHandlerContext { + client: ILegacyScopedClusterClient; +} + +/** + * @internal + */ +export interface RollupHandlerContext extends RequestHandlerContext { + rollup: RollupApiRequestHandlerContext; +} + +/** + * @internal + */ +export type RollupPluginRouter = IRouter; diff --git a/x-pack/plugins/saved_objects_tagging/server/plugin.ts b/x-pack/plugins/saved_objects_tagging/server/plugin.ts index ce687866711e4..4f291b7c033a2 100644 --- a/x-pack/plugins/saved_objects_tagging/server/plugin.ts +++ b/x-pack/plugins/saved_objects_tagging/server/plugin.ts @@ -17,7 +17,7 @@ import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/s import { SecurityPluginSetup } from '../../security/server'; import { savedObjectsTaggingFeature } from './features'; import { tagType } from './saved_objects'; -import { ITagsRequestHandlerContext } from './types'; +import type { TagsHandlerContext } from './types'; import { TagsRequestHandlerContext } from './request_handler_context'; import { registerRoutes } from './routes'; import { createTagUsageCollector } from './usage'; @@ -41,12 +41,12 @@ export class SavedObjectTaggingPlugin implements Plugin<{}, {}, SetupDeps, {}> { ) { savedObjects.registerType(tagType); - const router = http.createRouter(); + const router = http.createRouter(); registerRoutes({ router }); - http.registerRouteHandlerContext( + http.registerRouteHandlerContext( 'tags', - async (context, req, res): Promise => { + async (context, req, res) => { return new TagsRequestHandlerContext(req, context.core, security); } ); diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/assignments/find_assignable_objects.ts b/x-pack/plugins/saved_objects_tagging/server/routes/assignments/find_assignable_objects.ts index dcfb2f801eba9..86fa6d8a7c407 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/assignments/find_assignable_objects.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/assignments/find_assignable_objects.ts @@ -5,10 +5,10 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; import { FindAssignableObjectResponse } from '../../../common/http_api_types'; +import type { TagsPluginRouter } from '../../types'; -export const registerFindAssignableObjectsRoute = (router: IRouter) => { +export const registerFindAssignableObjectsRoute = (router: TagsPluginRouter) => { router.get( { path: '/internal/saved_objects_tagging/assignments/_find_assignable_objects', diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/assignments/get_assignable_types.ts b/x-pack/plugins/saved_objects_tagging/server/routes/assignments/get_assignable_types.ts index 182aa6d5ce43d..627244badbcee 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/assignments/get_assignable_types.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/assignments/get_assignable_types.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import type { TagsPluginRouter } from '../../types'; import { GetAssignableTypesResponse } from '../../../common/http_api_types'; -export const registerGetAssignableTypesRoute = (router: IRouter) => { +export const registerGetAssignableTypesRoute = (router: TagsPluginRouter) => { router.get( { path: '/internal/saved_objects_tagging/assignments/_assignable_types', diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/assignments/update_tags_assignments.ts b/x-pack/plugins/saved_objects_tagging/server/routes/assignments/update_tags_assignments.ts index 2144b7ffd99a9..b2179cd9b092a 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/assignments/update_tags_assignments.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/assignments/update_tags_assignments.ts @@ -5,10 +5,10 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { TagsPluginRouter } from '../../types'; import { AssignmentError } from '../../services'; -export const registerUpdateTagsAssignmentsRoute = (router: IRouter) => { +export const registerUpdateTagsAssignmentsRoute = (router: TagsPluginRouter) => { const objectReferenceSchema = schema.object({ type: schema.string(), id: schema.string(), diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/index.ts b/x-pack/plugins/saved_objects_tagging/server/routes/index.ts index bba2673b1ce0a..d1204ca96518a 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/index.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; import { registerUpdateTagRoute, registerGetAllTagsRoute, @@ -18,8 +17,9 @@ import { registerGetAssignableTypesRoute, } from './assignments'; import { registerInternalFindTagsRoute, registerInternalBulkDeleteRoute } from './internal'; +import { TagsPluginRouter } from '../types'; -export const registerRoutes = ({ router }: { router: IRouter }) => { +export const registerRoutes = ({ router }: { router: TagsPluginRouter }) => { // tags API registerCreateTagRoute(router); registerUpdateTagRoute(router); diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/internal/bulk_delete.ts b/x-pack/plugins/saved_objects_tagging/server/routes/internal/bulk_delete.ts index bade81678543d..68848f9716a2f 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/internal/bulk_delete.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/internal/bulk_delete.ts @@ -5,9 +5,9 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { TagsPluginRouter } from '../../types'; -export const registerInternalBulkDeleteRoute = (router: IRouter) => { +export const registerInternalBulkDeleteRoute = (router: TagsPluginRouter) => { router.post( { path: '/internal/saved_objects_tagging/tags/_bulk_delete', diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/internal/find_tags.ts b/x-pack/plugins/saved_objects_tagging/server/routes/internal/find_tags.ts index 6e095eb6e4a6e..e99d9c1da7b91 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/internal/find_tags.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/internal/find_tags.ts @@ -5,13 +5,13 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { TagsPluginRouter } from '../../types'; import { tagSavedObjectTypeName } from '../../../common/constants'; import { TagAttributes } from '../../../common/types'; import { savedObjectToTag } from '../../services/tags'; import { addConnectionCount } from '../lib'; -export const registerInternalFindTagsRoute = (router: IRouter) => { +export const registerInternalFindTagsRoute = (router: TagsPluginRouter) => { router.get( { path: '/internal/saved_objects_tagging/tags/_find', diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/tags/create_tag.ts b/x-pack/plugins/saved_objects_tagging/server/routes/tags/create_tag.ts index 499f73c3e0470..56b05a83a333b 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/tags/create_tag.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/tags/create_tag.ts @@ -5,10 +5,10 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { TagsPluginRouter } from '../../types'; import { TagValidationError } from '../../services/tags'; -export const registerCreateTagRoute = (router: IRouter) => { +export const registerCreateTagRoute = (router: TagsPluginRouter) => { router.post( { path: '/api/saved_objects_tagging/tags/create', diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/tags/delete_tag.ts b/x-pack/plugins/saved_objects_tagging/server/routes/tags/delete_tag.ts index 84f798063555e..656a97aece889 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/tags/delete_tag.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/tags/delete_tag.ts @@ -5,9 +5,9 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { TagsPluginRouter } from '../../types'; -export const registerDeleteTagRoute = (router: IRouter) => { +export const registerDeleteTagRoute = (router: TagsPluginRouter) => { router.delete( { path: '/api/saved_objects_tagging/tags/{id}', diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/tags/get_all_tags.ts b/x-pack/plugins/saved_objects_tagging/server/routes/tags/get_all_tags.ts index cbc43d66f0ecc..3603265beae51 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/tags/get_all_tags.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/tags/get_all_tags.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import type { TagsPluginRouter } from '../../types'; -export const registerGetAllTagsRoute = (router: IRouter) => { +export const registerGetAllTagsRoute = (router: TagsPluginRouter) => { router.get( { path: '/api/saved_objects_tagging/tags', diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/tags/get_tag.ts b/x-pack/plugins/saved_objects_tagging/server/routes/tags/get_tag.ts index 559c5ed2d8dd1..9f7401d6c1cd7 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/tags/get_tag.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/tags/get_tag.ts @@ -5,9 +5,9 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import type { TagsPluginRouter } from '../../types'; -export const registerGetTagRoute = (router: IRouter) => { +export const registerGetTagRoute = (router: TagsPluginRouter) => { router.get( { path: '/api/saved_objects_tagging/tags/{id}', diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/tags/update_tag.ts b/x-pack/plugins/saved_objects_tagging/server/routes/tags/update_tag.ts index fe8a48ae6e855..b81bc54c1b5dc 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/tags/update_tag.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/tags/update_tag.ts @@ -5,10 +5,10 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; import { TagValidationError } from '../../services/tags'; +import type { TagsPluginRouter } from '../../types'; -export const registerUpdateTagRoute = (router: IRouter) => { +export const registerUpdateTagRoute = (router: TagsPluginRouter) => { router.post( { path: '/api/saved_objects_tagging/tags/{id}', diff --git a/x-pack/plugins/saved_objects_tagging/server/types.ts b/x-pack/plugins/saved_objects_tagging/server/types.ts index de5997b84a75c..6e3e151ad8adf 100644 --- a/x-pack/plugins/saved_objects_tagging/server/types.ts +++ b/x-pack/plugins/saved_objects_tagging/server/types.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import type { IRouter, RequestHandlerContext } from 'src/core/server'; import { ITagsClient } from '../common/types'; import { IAssignmentService } from './services'; @@ -12,8 +13,14 @@ export interface ITagsRequestHandlerContext { assignmentService: IAssignmentService; } -declare module 'src/core/server' { - interface RequestHandlerContext { - tags?: ITagsRequestHandlerContext; - } +/** + * @internal + */ +export interface TagsHandlerContext extends RequestHandlerContext { + tags: ITagsRequestHandlerContext; } + +/** + * @internal + */ +export type TagsPluginRouter = IRouter; diff --git a/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts b/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts index 53950cb431941..f329d7a3980cc 100644 --- a/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts @@ -6,11 +6,7 @@ import Boom from '@hapi/boom'; import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; -import { - kibanaResponseFactory, - RequestHandler, - RequestHandlerContext, -} from '../../../../../../src/core/server'; +import { kibanaResponseFactory, RequestHandler } from '../../../../../../src/core/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; @@ -18,6 +14,7 @@ import { routeDefinitionParamsMock } from '../index.mock'; import type { AuthenticationServiceStart } from '../../authentication'; import { defineEnabledApiKeysRoutes } from './enabled'; import { authenticationServiceMock } from '../../authentication/authentication_service.mock'; +import type { SecurityRequestHandlerContext } from '../../types'; describe('API keys enabled', () => { function getMockContext( @@ -25,7 +22,7 @@ describe('API keys enabled', () => { ) { return ({ licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; + } as unknown) as SecurityRequestHandlerContext; } let routeHandler: RequestHandler; diff --git a/x-pack/plugins/security/server/routes/authentication/common.test.ts b/x-pack/plugins/security/server/routes/authentication/common.test.ts index b032930e4400a..4073ef9f21c60 100644 --- a/x-pack/plugins/security/server/routes/authentication/common.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/common.test.ts @@ -7,10 +7,8 @@ import { Type } from '@kbn/config-schema'; import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { - IRouter, kibanaResponseFactory, RequestHandler, - RequestHandlerContext, RouteConfig, } from '../../../../../../src/core/server'; import type { SecurityLicense, SecurityLicenseFeatures } from '../../../common/licensing'; @@ -22,6 +20,7 @@ import { SAMLLogin, } from '../../authentication'; import { defineCommonRoutes } from './common'; +import type { SecurityRequestHandlerContext, SecurityRouter } from '../../types'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; @@ -29,10 +28,10 @@ import { routeDefinitionParamsMock } from '../index.mock'; import { authenticationServiceMock } from '../../authentication/authentication_service.mock'; describe('Common authentication routes', () => { - let router: jest.Mocked; + let router: jest.Mocked; let authc: DeeplyMockedKeys; let license: jest.Mocked; - let mockContext: RequestHandlerContext; + let mockContext: SecurityRequestHandlerContext; beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); router = routeParamsMock.router; @@ -44,13 +43,13 @@ describe('Common authentication routes', () => { licensing: { license: { check: jest.fn().mockReturnValue({ check: 'valid' }) }, }, - } as unknown) as RequestHandlerContext; + } as unknown) as SecurityRequestHandlerContext; defineCommonRoutes(routeParamsMock); }); describe('logout', () => { - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; let routeConfig: RouteConfig; const mockRequest = httpServerMock.createKibanaRequest({ @@ -151,7 +150,7 @@ describe('Common authentication routes', () => { }); describe('me', () => { - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; let routeConfig: RouteConfig; const mockRequest = httpServerMock.createKibanaRequest({ @@ -185,7 +184,7 @@ describe('Common authentication routes', () => { }); describe('login', () => { - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; let routeConfig: RouteConfig; beforeEach(() => { const [acsRouteConfig, acsRouteHandler] = router.post.mock.calls.find( @@ -645,7 +644,7 @@ describe('Common authentication routes', () => { }); describe('acknowledge access agreement', () => { - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; let routeConfig: RouteConfig; beforeEach(() => { const [acsRouteConfig, acsRouteHandler] = router.post.mock.calls.find( diff --git a/x-pack/plugins/security/server/routes/authentication/saml.test.ts b/x-pack/plugins/security/server/routes/authentication/saml.test.ts index d1d5f601d7a43..ad7d141cad681 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.test.ts @@ -8,7 +8,8 @@ import { Type } from '@kbn/config-schema'; import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { AuthenticationResult, AuthenticationServiceStart, SAMLLogin } from '../../authentication'; import { defineSAMLRoutes } from './saml'; -import type { IRouter, RequestHandler, RouteConfig } from '../../../../../../src/core/server'; +import type { RequestHandler, RouteConfig } from '../../../../../../src/core/server'; +import type { SecurityRouter } from '../../types'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; @@ -16,7 +17,7 @@ import { routeDefinitionParamsMock } from '../index.mock'; import { authenticationServiceMock } from '../../authentication/authentication_service.mock'; describe('SAML authentication routes', () => { - let router: jest.Mocked; + let router: jest.Mocked; let authc: DeeplyMockedKeys; beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts index 7301a3cf51974..7e180f355ddfd 100644 --- a/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { RawKibanaPrivileges } from '../../../../common/model'; import { defineGetPrivilegesRoutes } from './get'; +import type { SecurityRequestHandlerContext } from '../../../types'; import { httpServerMock } from '../../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../../index.mock'; @@ -66,7 +67,7 @@ describe('GET privileges', () => { }); const mockContext = ({ licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; + } as unknown) as SecurityRequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); diff --git a/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts b/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts index ccdee8b100039..c3ceecef1fa68 100644 --- a/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts @@ -5,10 +5,8 @@ */ import { - IRouter, kibanaResponseFactory, RequestHandler, - RequestHandlerContext, RouteConfig, } from '../../../../../../../src/core/server'; import { defineShareSavedObjectPermissionRoutes } from './share_saved_object_permissions'; @@ -18,26 +16,27 @@ import { routeDefinitionParamsMock } from '../../index.mock'; import { RouteDefinitionParams } from '../..'; import { DeeplyMockedKeys } from '@kbn/utility-types/target/jest'; import { CheckPrivileges } from '../../../authorization/types'; +import type { SecurityRequestHandlerContext, SecurityRouter } from '../../../types'; describe('Share Saved Object Permissions', () => { - let router: jest.Mocked; + let router: jest.Mocked; let routeParamsMock: DeeplyMockedKeys; const mockContext = ({ licensing: { license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, }, - } as unknown) as RequestHandlerContext; + } as unknown) as SecurityRequestHandlerContext; beforeEach(() => { routeParamsMock = routeDefinitionParamsMock.create(); - router = routeParamsMock.router as jest.Mocked; + router = routeParamsMock.router as jest.Mocked; defineShareSavedObjectPermissionRoutes(routeParamsMock); }); describe('GET /internal/security/_share_saved_object_permissions', () => { - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; let routeConfig: RouteConfig; beforeEach(() => { const [shareRouteConfig, shareRouteHandler] = router.get.mock.calls.find( diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index 899215c49fa9f..2d49329fd63d3 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -5,13 +5,14 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; import type { KibanaFeature } from '../../../features/server'; -import type { HttpResources, IBasePath, IRouter, Logger } from '../../../../../src/core/server'; +import type { HttpResources, IBasePath, Logger } from '../../../../../src/core/server'; import type { SecurityLicense } from '../../common/licensing'; import type { AuthenticationServiceStart } from '../authentication'; import type { AuthorizationServiceSetup } from '../authorization'; import type { ConfigType } from '../config'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; import type { Session } from '../session_management'; +import type { SecurityRouter } from '../types'; import { defineAuthenticationRoutes } from './authentication'; import { defineAuthorizationRoutes } from './authorization'; @@ -26,7 +27,7 @@ import { defineViewRoutes } from './views'; * Describes parameters used to define HTTP routes. */ export interface RouteDefinitionParams { - router: IRouter; + router: SecurityRouter; basePath: IBasePath; httpResources: HttpResources; logger: Logger; diff --git a/x-pack/plugins/security/server/routes/licensed_route_handler.ts b/x-pack/plugins/security/server/routes/licensed_route_handler.ts index d8c212aa2d217..c1722c5b19833 100644 --- a/x-pack/plugins/security/server/routes/licensed_route_handler.ts +++ b/x-pack/plugins/security/server/routes/licensed_route_handler.ts @@ -5,17 +5,19 @@ */ import { KibanaResponseFactory, RequestHandler, RouteMethod } from 'kibana/server'; +import type { SecurityRequestHandlerContext } from '../types'; export const createLicensedRouteHandler = < P, Q, B, + Context extends SecurityRequestHandlerContext, M extends RouteMethod, R extends KibanaResponseFactory >( - handler: RequestHandler + handler: RequestHandler ) => { - const licensedRouteHandler: RequestHandler = ( + const licensedRouteHandler: RequestHandler = ( context, request, responseToolkit diff --git a/x-pack/plugins/security/server/routes/session_management/extend.test.ts b/x-pack/plugins/security/server/routes/session_management/extend.test.ts index 235fce152510c..8307905fb8842 100644 --- a/x-pack/plugins/security/server/routes/session_management/extend.test.ts +++ b/x-pack/plugins/security/server/routes/session_management/extend.test.ts @@ -5,19 +5,18 @@ */ import { - IRouter, kibanaResponseFactory, RequestHandler, - RequestHandlerContext, RouteConfig, } from '../../../../../../src/core/server'; import { defineSessionExtendRoutes } from './extend'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; +import type { SecurityRequestHandlerContext, SecurityRouter } from '../../types'; import { routeDefinitionParamsMock } from '../index.mock'; describe('Extend session routes', () => { - let router: jest.Mocked; + let router: jest.Mocked; beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); router = routeParamsMock.router; @@ -26,7 +25,7 @@ describe('Extend session routes', () => { }); describe('extend session', () => { - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; let routeConfig: RouteConfig; beforeEach(() => { const [extendRouteConfig, extendRouteHandler] = router.post.mock.calls.find( @@ -45,7 +44,7 @@ describe('Extend session routes', () => { it('always returns 302.', async () => { await expect( routeHandler( - ({} as unknown) as RequestHandlerContext, + ({} as unknown) as SecurityRequestHandlerContext, httpServerMock.createKibanaRequest(), kibanaResponseFactory ) diff --git a/x-pack/plugins/security/server/routes/session_management/info.test.ts b/x-pack/plugins/security/server/routes/session_management/info.test.ts index a104336f2e6ba..c51956f3fe530 100644 --- a/x-pack/plugins/security/server/routes/session_management/info.test.ts +++ b/x-pack/plugins/security/server/routes/session_management/info.test.ts @@ -5,22 +5,21 @@ */ import { - IRouter, kibanaResponseFactory, RequestHandler, - RequestHandlerContext, RouteConfig, } from '../../../../../../src/core/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { Session } from '../../session_management'; import { defineSessionInfoRoutes } from './info'; +import type { SecurityRequestHandlerContext, SecurityRouter } from '../../types'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { sessionMock } from '../../session_management/session.mock'; import { routeDefinitionParamsMock } from '../index.mock'; describe('Info session routes', () => { - let router: jest.Mocked; + let router: jest.Mocked; let session: jest.Mocked>; beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); @@ -31,7 +30,7 @@ describe('Info session routes', () => { }); describe('extend session', () => { - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; let routeConfig: RouteConfig; beforeEach(() => { const [extendRouteConfig, extendRouteHandler] = router.get.mock.calls.find( @@ -53,7 +52,11 @@ describe('Info session routes', () => { const request = httpServerMock.createKibanaRequest(); await expect( - routeHandler(({} as unknown) as RequestHandlerContext, request, kibanaResponseFactory) + routeHandler( + ({} as unknown) as SecurityRequestHandlerContext, + request, + kibanaResponseFactory + ) ).resolves.toEqual({ status: 500, options: {}, @@ -79,7 +82,7 @@ describe('Info session routes', () => { }; await expect( routeHandler( - ({} as unknown) as RequestHandlerContext, + ({} as unknown) as SecurityRequestHandlerContext, httpServerMock.createKibanaRequest(), kibanaResponseFactory ) @@ -95,7 +98,7 @@ describe('Info session routes', () => { await expect( routeHandler( - ({} as unknown) as RequestHandlerContext, + ({} as unknown) as SecurityRequestHandlerContext, httpServerMock.createKibanaRequest(), kibanaResponseFactory ) diff --git a/x-pack/plugins/security/server/routes/users/change_password.test.ts b/x-pack/plugins/security/server/routes/users/change_password.test.ts index 1ef4e407e45e2..24e73e456619b 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.test.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.test.ts @@ -8,12 +8,11 @@ import { errors } from 'elasticsearch'; import { ObjectType } from '@kbn/config-schema'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import type { SecurityRequestHandlerContext, SecurityRouter } from '../../types'; import { Headers, - IRouter, kibanaResponseFactory, RequestHandler, - RequestHandlerContext, RouteConfig, } from '../../../../../../src/core/server'; import { AuthenticationResult, AuthenticationServiceStart } from '../../authentication'; @@ -27,12 +26,12 @@ import { routeDefinitionParamsMock } from '../index.mock'; import { authenticationServiceMock } from '../../authentication/authentication_service.mock'; describe('Change password', () => { - let router: jest.Mocked; + let router: jest.Mocked; let authc: DeeplyMockedKeys; let session: jest.Mocked>; - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; let routeConfig: RouteConfig; - let mockContext: DeeplyMockedKeys; + let mockContext: DeeplyMockedKeys; function checkPasswordChangeAPICall(username: string, headers?: Headers) { expect( diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts index dfe5faa95ae15..4c4f8a22eee23 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts @@ -8,16 +8,15 @@ import { RequestHandler, RouteConfig, kibanaResponseFactory, - IRouter, HttpResources, HttpResourcesRequestHandler, - RequestHandlerContext, } from '../../../../../../src/core/server'; import { SecurityLicense, SecurityLicenseFeatures } from '../../../common/licensing'; import type { AuthenticationProvider } from '../../../common/model'; import { ConfigType } from '../../config'; import { Session } from '../../session_management'; import { defineAccessAgreementRoutes } from './access_agreement'; +import type { SecurityRouter, SecurityRequestHandlerContext } from '../../types'; import { httpResourcesMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { sessionMock } from '../../session_management/session.mock'; @@ -25,11 +24,11 @@ import { routeDefinitionParamsMock } from '../index.mock'; describe('Access agreement view routes', () => { let httpResources: jest.Mocked; - let router: jest.Mocked; + let router: jest.Mocked; let config: ConfigType; let session: jest.Mocked>; let license: jest.Mocked; - let mockContext: RequestHandlerContext; + let mockContext: SecurityRequestHandlerContext; beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); router = routeParamsMock.router; @@ -46,7 +45,7 @@ describe('Access agreement view routes', () => { licensing: { license: { check: jest.fn().mockReturnValue({ check: 'valid' }) }, }, - } as unknown) as RequestHandlerContext; + } as unknown) as SecurityRequestHandlerContext; defineAccessAgreementRoutes(routeParamsMock); }); @@ -93,7 +92,7 @@ describe('Access agreement view routes', () => { }); describe('Access agreement state route', () => { - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; let routeConfig: RouteConfig; beforeEach(() => { const [loginStateRouteConfig, loginStateRouteHandler] = router.get.mock.calls.find( diff --git a/x-pack/plugins/security/server/routes/views/capture_url.test.ts b/x-pack/plugins/security/server/routes/views/capture_url.test.ts index 2b2aab3407eb3..74a30cfb24654 100644 --- a/x-pack/plugins/security/server/routes/views/capture_url.test.ts +++ b/x-pack/plugins/security/server/routes/views/capture_url.test.ts @@ -9,12 +9,12 @@ import { RouteConfig, HttpResources, HttpResourcesRequestHandler, - RequestHandlerContext, } from '../../../../../../src/core/server'; import { defineCaptureURLRoutes } from './capture_url'; import { httpResourcesMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; +import type { SecurityRequestHandlerContext } from '../../types'; describe('Capture URL view routes', () => { let httpResources: jest.Mocked; @@ -92,7 +92,7 @@ describe('Capture URL view routes', () => { const request = httpServerMock.createKibanaRequest(); const responseFactory = httpResourcesMock.createResponseFactory(); - await routeHandler(({} as unknown) as RequestHandlerContext, request, responseFactory); + await routeHandler(({} as unknown) as SecurityRequestHandlerContext, request, responseFactory); expect(responseFactory.renderAnonymousCoreApp).toHaveBeenCalledWith(); }); diff --git a/x-pack/plugins/security/server/routes/views/login.test.ts b/x-pack/plugins/security/server/routes/views/login.test.ts index e79420fa05daf..dfaf935f64972 100644 --- a/x-pack/plugins/security/server/routes/views/login.test.ts +++ b/x-pack/plugins/security/server/routes/views/login.test.ts @@ -9,7 +9,6 @@ import { Type } from '@kbn/config-schema'; import { HttpResources, HttpResourcesRequestHandler, - IRouter, RequestHandler, kibanaResponseFactory, RouteConfig, @@ -18,6 +17,7 @@ import { SecurityLicense } from '../../../common/licensing'; import { LoginSelectorProvider } from '../../../common/login_state'; import { ConfigType } from '../../config'; import { defineLoginRoutes } from './login'; +import type { SecurityRouter, SecurityRequestHandlerContext } from '../../types'; import { coreMock, @@ -28,7 +28,7 @@ import { routeDefinitionParamsMock } from '../index.mock'; describe('Login view routes', () => { let httpResources: jest.Mocked; - let router: jest.Mocked; + let router: jest.Mocked; let license: jest.Mocked; let config: ConfigType; beforeEach(() => { @@ -145,7 +145,7 @@ describe('Login view routes', () => { return routeDefinitionParamsMock.create({ authc: { ...authcConfig } }).config.authc; } - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; let routeConfig: RouteConfig; beforeEach(() => { const [loginStateRouteConfig, loginStateRouteHandler] = router.get.mock.calls.find( diff --git a/x-pack/plugins/security/server/types.ts b/x-pack/plugins/security/server/types.ts new file mode 100644 index 0000000000000..74c4c6b9cab7f --- /dev/null +++ b/x-pack/plugins/security/server/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { LicensingApiRequestHandlerContext } from '../../licensing/server'; + +/** + * @internal + */ +export interface SecurityRequestHandlerContext extends RequestHandlerContext { + licensing: LicensingApiRequestHandlerContext; +} + +/** + * @internal + */ +export type SecurityRouter = IRouter; diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts index d2ce0908b91fc..d287ada74eebc 100644 --- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts @@ -15,7 +15,7 @@ import { getPackagePolicyCreateCallback, getPackagePolicyUpdateCallback, } from './ingest_integration'; -import { KibanaRequest, RequestHandlerContext } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { createMockConfig, requestContextMock } from '../lib/detection_engine/routes/__mocks__'; import { EndpointAppContextServiceStartContract } from './endpoint_app_context_services'; import { createMockEndpointAppContextServiceStartContract } from './mocks'; @@ -25,13 +25,14 @@ import { Subject } from 'rxjs'; import { ILicense } from '../../../licensing/common/types'; import { EndpointDocGenerator } from '../../common/endpoint/generate_data'; import { ProtectionModes } from '../../common/endpoint/types'; +import type { SecuritySolutionRequestHandlerContext } from '../types'; import { getExceptionListClientMock } from '../../../lists/server/services/exception_lists/exception_list_client.mock'; import { ExceptionListClient } from '../../../lists/server'; describe('ingest_integration tests ', () => { let endpointAppContextMock: EndpointAppContextServiceStartContract; let req: KibanaRequest; - let ctx: RequestHandlerContext; + let ctx: SecuritySolutionRequestHandlerContext; const exceptionListClient: ExceptionListClient = getExceptionListClientMock(); const maxTimelineImportExportSize = createMockConfig().maxTimelineImportExportSize; let licenseEmitter: Subject; diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts index 194d93df4b43b..1e7f440ed6788 100644 --- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts +++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts @@ -100,10 +100,15 @@ export const getPackagePolicyCreateCallback = ( // prep for detection rules creation const appClient = appClientFactory.create(request); + // This callback is called by fleet plugin. + // It doesn't have access to SecuritySolutionRequestHandlerContext in runtime. + // Muting the error to have green CI. + // @ts-expect-error const frameworkRequest = await buildFrameworkRequest(context, securitySetup, request); // Create detection index & rules (if necessary). move past any failure, this is just a convenience try { + // @ts-expect-error await createDetectionIndex(context, appClient); } catch (err) { if (err.statusCode !== 409) { @@ -117,6 +122,7 @@ export const getPackagePolicyCreateCallback = ( // this checks to make sure index exists first, safe to try in case of failure above // may be able to recover from minor errors await createPrepackagedRules( + // @ts-expect-error context, appClient, alerts.getAlertsClientWithRequest(request), diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index 2161ccf0eb780..04bca2f0627b5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -28,6 +28,7 @@ import { EndpointAppContext } from './types'; import { MetadataRequestContext } from './routes/metadata/handlers'; // import { licenseMock } from '../../../licensing/common/licensing.mock'; import { LicenseService } from '../../common/license/license'; +import { SecuritySolutionRequestHandlerContext } from '../types'; /** * Creates a mocked EndpointAppContext. @@ -118,7 +119,7 @@ export const createMockMetadataRequestContext = (): jest.Mocked, }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.test.ts index 4454da855569b..cb26339e3b0f0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.test.ts @@ -5,7 +5,7 @@ */ import { deflateSync, inflateSync } from 'zlib'; import LRU from 'lru-cache'; -import { +import type { ILegacyClusterClient, IRouter, SavedObjectsClientContract, @@ -13,7 +13,6 @@ import { RouteConfig, RequestHandler, KibanaResponseFactory, - RequestHandlerContext, SavedObject, } from 'kibana/server'; import { @@ -29,6 +28,7 @@ import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { createMockEndpointAppContextServiceStartContract } from '../../mocks'; import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; import { WrappedTranslatedExceptionList } from '../../schemas/artifacts/lists'; +import type { SecuritySolutionRequestHandlerContext } from '../../../types'; const mockArtifactName = `${ArtifactConstants.GLOBAL_ALLOWLIST_NAME}-windows-v1`; const expectedEndpointExceptions: WrappedTranslatedExceptionList = { @@ -173,7 +173,7 @@ describe('test alerts route', () => { client: mockSavedObjectClient, }, }, - } as unknown) as RequestHandlerContext, + } as unknown) as SecuritySolutionRequestHandlerContext, mockRequest, mockResponse ); @@ -217,7 +217,7 @@ describe('test alerts route', () => { client: mockSavedObjectClient, }, }, - } as unknown) as RequestHandlerContext, + } as unknown) as SecuritySolutionRequestHandlerContext, mockRequest, mockResponse ); @@ -251,7 +251,7 @@ describe('test alerts route', () => { client: mockSavedObjectClient, }, }, - } as unknown) as RequestHandlerContext, + } as unknown) as SecuritySolutionRequestHandlerContext, mockRequest, mockResponse ); @@ -279,7 +279,7 @@ describe('test alerts route', () => { client: mockSavedObjectClient, }, }, - } as unknown) as RequestHandlerContext, + } as unknown) as SecuritySolutionRequestHandlerContext, mockRequest, mockResponse ); @@ -313,7 +313,7 @@ describe('test alerts route', () => { client: mockSavedObjectClient, }, }, - } as unknown) as RequestHandlerContext, + } as unknown) as SecuritySolutionRequestHandlerContext, mockRequest, mockResponse ); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index 83732170fb5c3..4a94e24224397 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -5,7 +5,7 @@ */ import Boom from '@hapi/boom'; -import { RequestHandlerContext, Logger, RequestHandler } from 'kibana/server'; +import type { Logger, RequestHandler } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; import { HostInfo, @@ -14,6 +14,8 @@ import { HostStatus, MetadataQueryStrategyVersions, } from '../../../../common/endpoint/types'; +import type { SecuritySolutionRequestHandlerContext } from '../../../types'; + import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders'; import { Agent, AgentStatus, PackagePolicy } from '../../../../../fleet/common/types/models'; import { EndpointAppContext, HostListQueryResult } from '../../types'; @@ -25,7 +27,7 @@ import { EndpointAppContextService } from '../../endpoint_app_context_services'; export interface MetadataRequestContext { endpointAppContextService: EndpointAppContextService; logger: Logger; - requestHandlerContext: RequestHandlerContext; + requestHandlerContext: SecuritySolutionRequestHandlerContext; } const HOST_STATUS_MAPPING = new Map([ @@ -52,7 +54,12 @@ export const getMetadataListRequestHandler = function ( endpointAppContext: EndpointAppContext, logger: Logger, queryStrategyVersion?: MetadataQueryStrategyVersions -): RequestHandler> { +): RequestHandler< + unknown, + unknown, + TypeOf, + SecuritySolutionRequestHandlerContext +> { return async (context, request, response) => { try { const agentService = endpointAppContext.service.getAgentService(); @@ -112,7 +119,12 @@ export const getMetadataRequestHandler = function ( endpointAppContext: EndpointAppContext, logger: Logger, queryStrategyVersion?: MetadataQueryStrategyVersions -): RequestHandler, undefined, undefined> { +): RequestHandler< + TypeOf, + unknown, + unknown, + SecuritySolutionRequestHandlerContext +> { return async (context, request, response) => { const agentService = endpointAppContext.service.getAgentService(); if (agentService === undefined) { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts index bfb2a6a828e68..6bcde49ea80b4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; import { HostStatus, MetadataQueryStrategyVersions } from '../../../../common/endpoint/types'; import { EndpointAppContext } from '../../types'; import { getLogger, getMetadataListRequestHandler, getMetadataRequestHandler } from './handlers'; +import type { SecuritySolutionPluginRouter } from '../../../types'; export const BASE_ENDPOINT_ROUTE = '/api/endpoint'; export const METADATA_REQUEST_V1_ROUTE = `${BASE_ENDPOINT_ROUTE}/v1/metadata`; @@ -60,7 +60,10 @@ export const GetMetadataListRequestSchema = { ), }; -export function registerEndpointRoutes(router: IRouter, endpointAppContext: EndpointAppContext) { +export function registerEndpointRoutes( + router: SecuritySolutionPluginRouter, + endpointAppContext: EndpointAppContext +) { const logger = getLogger(endpointAppContext); router.post( { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 25de64aac5258..5049104a96401 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -5,7 +5,6 @@ */ import { ILegacyClusterClient, - IRouter, ILegacyScopedClusterClient, KibanaResponseFactory, RequestHandler, @@ -43,16 +42,17 @@ import { import { createV1SearchResponse, createV2SearchResponse } from './support/test_support'; import { PackageService } from '../../../../../fleet/server/services'; import { metadataTransformPrefix } from '../../../../common/endpoint/constants'; +import type { SecuritySolutionPluginRouter } from '../../../types'; describe('test endpoint route', () => { - let routerMock: jest.Mocked; + let routerMock: jest.Mocked; let mockResponse: jest.Mocked; let mockClusterClient: jest.Mocked; let mockScopedClient: jest.Mocked; let mockSavedObjectClient: jest.Mocked; let mockPackageService: jest.Mocked; // eslint-disable-next-line @typescript-eslint/no-explicit-any - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; // eslint-disable-next-line @typescript-eslint/no-explicit-any let routeConfig: RouteConfig; // tests assume that fleet is enabled, and thus agentService is available diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts index 44776bfddd61a..b879272862d48 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts @@ -5,7 +5,6 @@ */ import { ILegacyClusterClient, - IRouter, ILegacyScopedClusterClient, KibanaResponseFactory, RequestHandler, @@ -38,16 +37,17 @@ import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data' import { Agent, EsAssetReference } from '../../../../../fleet/common/types/models'; import { createV1SearchResponse } from './support/test_support'; import { PackageService } from '../../../../../fleet/server/services'; +import type { SecuritySolutionPluginRouter } from '../../../types'; describe('test endpoint route v1', () => { - let routerMock: jest.Mocked; + let routerMock: jest.Mocked; let mockResponse: jest.Mocked; let mockClusterClient: jest.Mocked; let mockScopedClient: jest.Mocked; let mockSavedObjectClient: jest.Mocked; let mockPackageService: jest.Mocked; // eslint-disable-next-line @typescript-eslint/no-explicit-any - let routeHandler: RequestHandler; + let routeHandler: RequestHandler; // eslint-disable-next-line @typescript-eslint/no-explicit-any let routeConfig: RouteConfig; // tests assume that fleet is enabled, and thus agentService is available diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts index de78313c6ca2b..feee872b90acd 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts @@ -22,6 +22,7 @@ import { getTrustedAppsListRouteHandler, getTrustedAppsSummaryRouteHandler, } from './handlers'; +import type { SecuritySolutionRequestHandlerContext } from '../../../types'; const exceptionsListClient = listMock.getExceptionListClient() as jest.Mocked; @@ -31,13 +32,14 @@ const createAppContextMock = () => ({ config: () => Promise.resolve(createMockConfig()), }); -const createHandlerContextMock = () => ({ - ...xpackMocks.createRequestHandlerContext(), - lists: { - getListClient: jest.fn(), - getExceptionListClient: jest.fn().mockReturnValue(exceptionsListClient), - }, -}); +const createHandlerContextMock = () => + (({ + ...xpackMocks.createRequestHandlerContext(), + lists: { + getListClient: jest.fn(), + getExceptionListClient: jest.fn().mockReturnValue(exceptionsListClient), + }, + } as unknown) as jest.Mocked); const assertResponse = ( response: jest.Mocked, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts index aa2167a493294..825ae57b2fa44 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandler, RequestHandlerContext } from 'kibana/server'; +import type { RequestHandler } from 'kibana/server'; +import type { SecuritySolutionRequestHandlerContext } from '../../../types'; import { ExceptionListClient } from '../../../../../lists/server'; @@ -23,7 +24,9 @@ import { MissingTrustedAppException, } from './service'; -const exceptionListClientFromContext = (context: RequestHandlerContext): ExceptionListClient => { +const exceptionListClientFromContext = ( + context: SecuritySolutionRequestHandlerContext +): ExceptionListClient => { const exceptionLists = context.lists?.getExceptionListClient(); if (!exceptionLists) { @@ -35,7 +38,12 @@ const exceptionListClientFromContext = (context: RequestHandlerContext): Excepti export const getTrustedAppsDeleteRouteHandler = ( endpointAppContext: EndpointAppContext -): RequestHandler => { +): RequestHandler< + DeleteTrustedAppsRequestParams, + unknown, + unknown, + SecuritySolutionRequestHandlerContext +> => { const logger = endpointAppContext.logFactory.get('trusted_apps'); return async (context, req, res) => { @@ -56,7 +64,12 @@ export const getTrustedAppsDeleteRouteHandler = ( export const getTrustedAppsListRouteHandler = ( endpointAppContext: EndpointAppContext -): RequestHandler => { +): RequestHandler< + unknown, + GetTrustedAppsListRequest, + unknown, + SecuritySolutionRequestHandlerContext +> => { const logger = endpointAppContext.logFactory.get('trusted_apps'); return async (context, req, res) => { @@ -73,7 +86,12 @@ export const getTrustedAppsListRouteHandler = ( export const getTrustedAppsCreateRouteHandler = ( endpointAppContext: EndpointAppContext -): RequestHandler => { +): RequestHandler< + unknown, + unknown, + PostTrustedAppCreateRequest, + SecuritySolutionRequestHandlerContext +> => { const logger = endpointAppContext.logFactory.get('trusted_apps'); return async (context, req, res) => { @@ -90,7 +108,12 @@ export const getTrustedAppsCreateRouteHandler = ( export const getTrustedAppsSummaryRouteHandler = ( endpointAppContext: EndpointAppContext -): RequestHandler => { +): RequestHandler< + unknown, + unknown, + PostTrustedAppCreateRequest, + SecuritySolutionRequestHandlerContext +> => { const logger = endpointAppContext.logFactory.get('trusted_apps'); return async (context, req, res) => { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts index ce5f571ce9be3..7e318030f1f75 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; import { DeleteTrustedAppsRequestSchema, GetTrustedAppsRequestSchema, @@ -23,9 +22,10 @@ import { getTrustedAppsSummaryRouteHandler, } from './handlers'; import { EndpointAppContext } from '../../types'; +import { SecuritySolutionPluginRouter } from '../../../types'; export const registerTrustedAppsRoutes = ( - router: IRouter, + router: SecuritySolutionPluginRouter, endpointAppContext: EndpointAppContext ) => { // DELETE one diff --git a/x-pack/plugins/security_solution/server/index.ts b/x-pack/plugins/security_solution/server/index.ts index 94764fd159360..0fa037ca77d04 100644 --- a/x-pack/plugins/security_solution/server/index.ts +++ b/x-pack/plugins/security_solution/server/index.ts @@ -57,3 +57,4 @@ export { getIndexExists } from './lib/detection_engine/index/get_index_exists'; export { buildRouteValidation } from './utils/build_validation/route_validation'; export { transformError, buildSiemResponse } from './lib/detection_engine/routes/utils'; export { readPrivileges } from './lib/detection_engine/privileges/read_privileges'; +export type { AppRequestContext } from './types'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index 8e379e5caa89e..511897094a1ec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionRequestHandlerContext } from '../../../../types'; import { coreMock, elasticsearchServiceMock, @@ -40,7 +40,7 @@ const createRequestContextMock = ( }, licensing: clients.licensing, securitySolution: { getAppClient: jest.fn(() => clients.appClient) }, - } as unknown) as RequestHandlerContext; + } as unknown) as SecuritySolutionRequestHandlerContext; }; const createTools = () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/server.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/server.ts index d0683f3baabe9..37b18fbcc2b07 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/server.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/server.ts @@ -8,13 +8,13 @@ import { RequestHandler, RouteConfig, KibanaRequest, - RequestHandlerContext, } from '../../../../../../../../src/core/server'; import { httpServiceMock } from '../../../../../../../../src/core/server/mocks'; import { requestContextMock } from './request_context'; import { responseMock as responseFactoryMock } from './response_factory'; import { requestMock } from '.'; import { responseAdapter } from './test_adapters'; +import { SecuritySolutionRequestHandlerContext } from '../../../../types'; interface Route { config: RouteConfig; @@ -53,7 +53,10 @@ class MockServer { return this.resultMock; } - public async inject(request: KibanaRequest, context: RequestHandlerContext = this.contextMock) { + public async inject( + request: KibanaRequest, + context: SecuritySolutionRequestHandlerContext = this.contextMock + ) { const validatedRequest = this.validateRequest(request); const [rejection] = this.resultMock.badRequest.mock.calls; if (rejection) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts index 8280e86bdf2cc..81be361ed4856 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AppClient } from '../../../../types'; -import { IRouter, RequestHandlerContext } from '../../../../../../../../src/core/server'; +import type { + AppClient, + SecuritySolutionPluginRouter, + SecuritySolutionRequestHandlerContext, +} from '../../../../types'; import { DETECTION_ENGINE_INDEX_URL } from '../../../../../common/constants'; import { transformError, buildSiemResponse } from '../utils'; import { getIndexExists } from '../../index/get_index_exists'; @@ -20,7 +23,7 @@ import { templateNeedsUpdate } from './check_template_version'; import { getIndexVersion } from './get_index_version'; import { isOutdated } from '../../migrations/helpers'; -export const createIndexRoute = (router: IRouter) => { +export const createIndexRoute = (router: SecuritySolutionPluginRouter) => { router.post( { path: DETECTION_ENGINE_INDEX_URL, @@ -59,7 +62,7 @@ class CreateIndexError extends Error { } export const createDetectionIndex = async ( - context: RequestHandlerContext, + context: SecuritySolutionRequestHandlerContext, siemClient: AppClient ): Promise => { const clusterClient = context.core.elasticsearch.legacy.client; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/delete_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/delete_index_route.ts index b58103bf15f3b..6b9a96b5efe2c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/delete_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/delete_index_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_INDEX_URL } from '../../../../../common/constants'; import { transformError, buildSiemResponse } from '../utils'; import { getIndexExists } from '../../index/get_index_exists'; @@ -24,7 +24,7 @@ import { deleteTemplate } from '../../index/delete_template'; * * And ensuring they're all gone */ -export const deleteIndexRoute = (router: IRouter) => { +export const deleteIndexRoute = (router: SecuritySolutionPluginRouter) => { router.delete( { path: DETECTION_ENGINE_INDEX_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts index d898d3fd59240..908313ad88e3c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_INDEX_URL } from '../../../../../common/constants'; import { transformError, buildSiemResponse } from '../utils'; import { getIndexExists } from '../../index/get_index_exists'; @@ -12,7 +12,7 @@ import { SIGNALS_TEMPLATE_VERSION } from './get_signals_template'; import { getIndexVersion } from './get_index_version'; import { isOutdated } from '../../migrations/helpers'; -export const readIndexRoute = (router: IRouter) => { +export const readIndexRoute = (router: SecuritySolutionPluginRouter) => { router.get( { path: DETECTION_ENGINE_INDEX_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts index 945be0c584134..76e68d3cd9b33 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts @@ -74,6 +74,7 @@ describe('read_privileges route', () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; const response = await server.inject( getPrivilegeRequest({ auth: { isAuthenticated: false } }), + // @ts-expect-error contextWithoutSecuritySolution ); expect(response.status).toEqual(404); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.ts index 174aa4911ba1e..58d8ef26faff6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/privileges/read_privileges_route.ts @@ -6,12 +6,15 @@ import { merge } from 'lodash/fp'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_PRIVILEGES_URL } from '../../../../../common/constants'; import { buildSiemResponse, transformError } from '../utils'; import { readPrivileges } from '../../privileges/read_privileges'; -export const readPrivilegesRoute = (router: IRouter, usingEphemeralEncryptionKey: boolean) => { +export const readPrivilegesRoute = ( + router: SecuritySolutionPluginRouter, + usingEphemeralEncryptionKey: boolean +) => { router.get( { path: DETECTION_ENGINE_PRIVILEGES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts index 3dc25583579c5..a4c35c87ca3bc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -141,6 +141,7 @@ describe('add_prepackaged_rules_route', () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; const response = await server.inject( addPrepackagedRulesRequest(), + // @ts-expect-error contextWithoutSecuritySolution ); expect(response.status).toEqual(404); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 210ec87ade2dc..a360d43d5016d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AppClient } from '../../../../types'; -import { IRouter, RequestHandlerContext } from '../../../../../../../../src/core/server'; +import type { + AppClient, + SecuritySolutionPluginRouter, + SecuritySolutionRequestHandlerContext, +} from '../../../../types'; import { validate } from '../../../../../common/validate'; import { @@ -35,7 +38,7 @@ import { FrameworkRequest } from '../../../framework'; import { ExceptionListClient } from '../../../../../../lists/server'; export const addPrepackedRulesRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, security: SetupPlugins['security'] ) => { @@ -87,7 +90,7 @@ class PrepackagedRulesError extends Error { } export const createPrepackagedRules = async ( - context: RequestHandlerContext, + context: SecuritySolutionRequestHandlerContext, siemClient: AppClient, alertsClient: AlertsClient, frameworkRequest: FrameworkRequest, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts index 55317fc28afca..31dca3646231d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts @@ -54,6 +54,7 @@ describe('create_rules_bulk', () => { it('returns 404 if siem client is unavailable', async () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; + // @ts-expect-error const response = await server.inject(getReadBulkRequest(), contextWithoutSecuritySolution); expect(response.status).toEqual(404); expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 3473948b000c7..6795abc5c211c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -8,7 +8,7 @@ import { validate } from '../../../../../common/validate'; import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents'; import { createRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; @@ -25,7 +25,10 @@ import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters' import { RuleTypeParams } from '../../types'; import { Alert } from '../../../../../../alerts/common'; -export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => { +export const createRulesBulkRoute = ( + router: SecuritySolutionPluginRouter, + ml: SetupPlugins['ml'] +) => { router.post( { path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index 40465f4dc7456..4f85ecb789a57 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -63,6 +63,7 @@ describe('create_rules', () => { it('returns 404 if siem client is unavailable', async () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; + // @ts-expect-error const response = await server.inject(getCreateRequest(), contextWithoutSecuritySolution); expect(response.status).toEqual(404); expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts index c59d5d2a36fd5..bfe32b9ae9a02 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -5,9 +5,9 @@ */ import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { buildMlAuthz } from '../../../machine_learning/authz'; import { throwHttpError } from '../../../machine_learning/validation'; import { readRules } from '../../rules/read_rules'; @@ -22,7 +22,10 @@ import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters' import { RuleTypeParams } from '../../types'; import { Alert } from '../../../../../../alerts/common'; -export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void => { +export const createRulesRoute = ( + router: SecuritySolutionPluginRouter, + ml: SetupPlugins['ml'] +): void => { router.post( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts index 99bf16aadc815..f4d9ac0a4ee84 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts @@ -12,7 +12,11 @@ import { QueryRulesBulkSchemaDecoded, } from '../../../../../common/detection_engine/schemas/request/query_rules_bulk_schema'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; -import { IRouter, RouteConfig, RequestHandler } from '../../../../../../../../src/core/server'; +import type { RouteConfig, RequestHandler } from '../../../../../../../../src/core/server'; +import type { + SecuritySolutionPluginRouter, + SecuritySolutionRequestHandlerContext, +} from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { getIdBulkError } from './utils'; import { transformValidateBulkError } from './validate'; @@ -23,9 +27,15 @@ import { deleteRuleActionsSavedObject } from '../../rule_actions/delete_rule_act import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; type Config = RouteConfig; -type Handler = RequestHandler; +type Handler = RequestHandler< + unknown, + unknown, + QueryRulesBulkSchemaDecoded, + SecuritySolutionRequestHandlerContext, + 'delete' | 'post' +>; -export const deleteRulesBulkRoute = (router: IRouter) => { +export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter) => { const config: Config = { validate: { body: buildRouteValidation( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts index f4aa51c6dcfc3..5eeaed6e9561f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts @@ -10,7 +10,7 @@ import { QueryRulesSchemaDecoded, } from '../../../../../common/detection_engine/schemas/request/query_rules_schema'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { deleteRules } from '../../rules/delete_rules'; import { getIdError } from './utils'; @@ -20,7 +20,7 @@ import { deleteNotifications } from '../../notifications/delete_notifications'; import { deleteRuleActionsSavedObject } from '../../rule_actions/delete_rule_actions_saved_object'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; -export const deleteRulesRoute = (router: IRouter) => { +export const deleteRulesRoute = (router: SecuritySolutionPluginRouter) => { router.delete( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts index 8df9f114559a8..9828747ca9523 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts @@ -11,7 +11,7 @@ import { ExportRulesSchemaDecoded, } from '../../../../../common/detection_engine/schemas/request/export_rules_schema'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { ConfigType } from '../../../../config'; import { getNonPackagedRulesCount } from '../../rules/get_existing_prepackaged_rules'; @@ -19,7 +19,7 @@ import { getExportByObjectIds } from '../../rules/get_export_by_object_ids'; import { getExportAll } from '../../rules/get_export_all'; import { transformError, buildSiemResponse } from '../utils'; -export const exportRulesRoute = (router: IRouter, config: ConfigType) => { +export const exportRulesRoute = (router: SecuritySolutionPluginRouter, config: ConfigType) => { router.post( { path: `${DETECTION_ENGINE_RULES_URL}/_export`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts index b2074ad20b674..b32497f52d573 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts @@ -9,7 +9,7 @@ import { findRulesSchema, FindRulesSchemaDecoded, } from '../../../../../common/detection_engine/schemas/request/find_rules_schema'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { findRules } from '../../rules/find_rules'; import { transformValidateFindAlerts } from './validate'; @@ -18,7 +18,7 @@ import { getRuleActionsSavedObject } from '../../rule_actions/get_rule_actions_s import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -export const findRulesRoute = (router: IRouter) => { +export const findRulesRoute = (router: SecuritySolutionPluginRouter) => { router.get( { path: `${DETECTION_ENGINE_RULES_URL}/_find`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts index 3ae228c165a56..add8e5435283f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts @@ -5,7 +5,7 @@ */ import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { RuleStatusResponse } from '../../rules/types'; import { transformError, buildSiemResponse, mergeStatuses, getFailingRules } from '../utils'; @@ -22,7 +22,7 @@ import { * @param router * @returns RuleStatusResponse */ -export const findRulesStatusesRoute = (router: IRouter) => { +export const findRulesStatusesRoute = (router: SecuritySolutionPluginRouter) => { router.post( { path: `${DETECTION_ENGINE_RULES_URL}/_find_statuses`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts index 4cd5238ccb1ef..45ed207b8bbe8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts @@ -9,7 +9,7 @@ import { PrePackagedRulesAndTimelinesStatusSchema, prePackagedRulesAndTimelinesStatusSchema, } from '../../../../../common/detection_engine/schemas/response/prepackaged_rules_status_schema'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constants'; import { transformError, buildSiemResponse } from '../utils'; import { getPrepackagedRules } from '../../rules/get_prepackaged_rules'; @@ -24,7 +24,7 @@ import { checkTimelinesStatus } from '../../../timeline/routes/utils/check_timel import { checkTimelineStatusRt } from '../../../timeline/routes/schemas/check_timelines_status_schema'; export const getPrepackagedRulesStatusRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, security: SetupPlugins['security'] ) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts index a033c16cd5e99..367ad866ae55f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts @@ -76,6 +76,7 @@ describe('import_rules_route', () => { it('returns 404 if siem client is unavailable', async () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; + // @ts-expect-error const response = await server.inject(request, contextWithoutSecuritySolution); expect(response.status).toEqual(404); expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts index adf027a430f8a..b782ba1a071de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -20,7 +20,7 @@ import { importRulesSchema as importRulesResponseSchema, } from '../../../../../common/detection_engine/schemas/response/import_rules_schema'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { ConfigType } from '../../../../config'; import { SetupPlugins } from '../../../../plugin'; @@ -49,7 +49,11 @@ type PromiseFromStreams = ImportRulesSchemaDecoded | Error; const CHUNK_PARSED_OBJECT_SIZE = 50; -export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupPlugins['ml']) => { +export const importRulesRoute = ( + router: SecuritySolutionPluginRouter, + config: ConfigType, + ml: SetupPlugins['ml'] +) => { router.post( { path: `${DETECTION_ENGINE_RULES_URL}/_import`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index 7dfb4daa1a0a2..380b9f2ae2c3c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -12,7 +12,7 @@ import { } from '../../../../../common/detection_engine/schemas/request/patch_rules_bulk_schema'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; @@ -26,7 +26,10 @@ import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_s import { readRules } from '../../rules/read_rules'; import { PartialFilter } from '../../types'; -export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => { +export const patchRulesBulkRoute = ( + router: SecuritySolutionPluginRouter, + ml: SetupPlugins['ml'] +) => { router.patch( { path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts index aadb13ef54e72..66a873860189b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -11,7 +11,7 @@ import { PatchRulesSchemaDecoded, patchRulesSchema, } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; @@ -25,7 +25,7 @@ import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_s import { readRules } from '../../rules/read_rules'; import { PartialFilter } from '../../types'; -export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { +export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.patch( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts index 6fb82166d416b..a1f081ffb9e0e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts @@ -10,7 +10,7 @@ import { QueryRulesSchemaDecoded, } from '../../../../../common/detection_engine/schemas/request/query_rules_schema'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { getIdError } from './utils'; import { transformValidate } from './validate'; @@ -19,7 +19,7 @@ import { readRules } from '../../rules/read_rules'; import { getRuleActionsSavedObject } from '../../rule_actions/get_rule_actions_saved_object'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; -export const readRulesRoute = (router: IRouter) => { +export const readRulesRoute = (router: SecuritySolutionPluginRouter) => { router.get( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts index 72583a5a78709..3da78c2f9736d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts @@ -68,6 +68,7 @@ describe('update_rules_bulk', () => { it('returns 404 if siem client is unavailable', async () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; + // @ts-expect-error const response = await server.inject(getUpdateBulkRequest(), contextWithoutSecuritySolution); expect(response.status).toEqual(404); expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index 5f9789220bc10..dc5f7855cae29 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -9,7 +9,7 @@ import { updateRuleValidateTypeDependents } from '../../../../../common/detectio import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { updateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/update_rules_bulk_schema'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; @@ -21,7 +21,10 @@ import { updateRules } from '../../rules/update_rules'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; -export const updateRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => { +export const updateRulesBulkRoute = ( + router: SecuritySolutionPluginRouter, + ml: SetupPlugins['ml'] +) => { router.put( { path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts index 96710b6f1d763..0c1500fecd6e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts @@ -75,6 +75,7 @@ describe('update_rules', () => { it('returns 404 if siem client is unavailable', async () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; + // @ts-expect-error const response = await server.inject(getUpdateRequest(), contextWithoutSecuritySolution); expect(response.status).toEqual(404); expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts index aa85747e3ce41..d3b3b23627a9e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -6,7 +6,7 @@ import { updateRulesSchema } from '../../../../../common/detection_engine/schemas/request'; import { updateRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/update_rules_type_dependents'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; @@ -19,7 +19,7 @@ import { updateRulesNotifications } from '../../rules/update_rules_notifications import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -export const updateRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { +export const updateRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.put( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts index 313cc37b20d88..4d3394cb0b775 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { SetupPlugins } from '../../../../plugin'; import { DETECTION_ENGINE_SIGNALS_MIGRATION_URL } from '../../../../../common/constants'; import { createSignalsMigrationSchema } from '../../../../../common/detection_engine/schemas/request/create_signals_migration_schema'; @@ -20,7 +20,7 @@ import { getSignalVersionsByIndex } from '../../migrations/get_signal_versions_b import { SIGNALS_TEMPLATE_VERSION } from '../index/get_signals_template'; export const createSignalsMigrationRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, security: SetupPlugins['security'] ) => { router.post( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts index 2515a5fabe992..b1be5d5df6bfc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { SetupPlugins } from '../../../../plugin'; import { DETECTION_ENGINE_SIGNALS_MIGRATION_URL } from '../../../../../common/constants'; import { deleteSignalsMigrationSchema } from '../../../../../common/detection_engine/schemas/request/delete_signals_migration_schema'; @@ -14,7 +14,7 @@ import { signalsMigrationService } from '../../migrations/migration_service'; import { getMigrationSavedObjectsById } from '../../migrations/get_migration_saved_objects_by_id'; export const deleteSignalsMigrationRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, security: SetupPlugins['security'] ) => { router.delete( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts index 2c02c0768dadf..186116bdc97e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { SetupPlugins } from '../../../../plugin'; import { DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL } from '../../../../../common/constants'; import { finalizeSignalsMigrationSchema } from '../../../../../common/detection_engine/schemas/request/finalize_signals_migration_schema'; @@ -16,7 +16,7 @@ import { buildSiemResponse, transformError } from '../utils'; import { getMigrationSavedObjectsById } from '../../migrations/get_migration_saved_objects_by_id'; export const finalizeSignalsMigrationRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, security: SetupPlugins['security'] ) => { router.post( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts index ed6546b0bf4f1..d36fa643964ab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL } from '../../../../../common/constants'; import { getSignalsMigrationStatusSchema } from '../../../../../common/detection_engine/schemas/request/get_signals_migration_status_schema'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; @@ -17,7 +17,7 @@ import { isOutdated, signalsAreOutdated } from '../../migrations/helpers'; import { getTemplateVersion } from '../index/check_template_version'; import { buildSiemResponse, transformError } from '../utils'; -export const getSignalsMigrationStatusRoute = (router: IRouter) => { +export const getSignalsMigrationStatusRoute = (router: SecuritySolutionPluginRouter) => { router.get( { path: DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts index 97b63025a86eb..5db9c75b6371e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts @@ -44,6 +44,7 @@ describe('set signal status', () => { const { securitySolution, ...contextWithoutSecuritySolution } = context; const response = await server.inject( getSetSignalStatusByQueryRequest(), + // @ts-expect-error contextWithoutSecuritySolution ); expect(response.status).toEqual(404); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index be6e57aee6d0c..4201a0d1ebe57 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -9,12 +9,12 @@ import { SetSignalsStatusSchemaDecoded, setSignalsStatusSchema, } from '../../../../../common/detection_engine/schemas/request/set_signal_status_schema'; -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../../common/constants'; import { transformError, buildSiemResponse } from '../utils'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -export const setSignalsStatusRoute = (router: IRouter) => { +export const setSignalsStatusRoute = (router: SecuritySolutionPluginRouter) => { router.post( { path: DETECTION_ENGINE_SIGNALS_STATUS_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts index 3ab4775f890ac..d1010a80c5b98 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../common/constants'; import { transformError, buildSiemResponse } from '../utils'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; @@ -14,7 +14,7 @@ import { QuerySignalsSchemaDecoded, } from '../../../../../common/detection_engine/schemas/request/query_signals_index_schema'; -export const querySignalsRoute = (router: IRouter) => { +export const querySignalsRoute = (router: SecuritySolutionPluginRouter) => { router.post( { path: DETECTION_ENGINE_QUERY_SIGNALS_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts index 9c508f99244cb..9be3a3b5f2f98 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from '../../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_TAGS_URL } from '../../../../../common/constants'; import { transformError, buildSiemResponse } from '../utils'; import { readTags } from '../../tags/read_tags'; -export const readTagsRoute = (router: IRouter) => { +export const readTagsRoute = (router: SecuritySolutionPluginRouter) => { router.get( { path: DETECTION_ENGINE_TAGS_URL, diff --git a/x-pack/plugins/security_solution/server/lib/framework/kibana_framework_adapter.ts b/x-pack/plugins/security_solution/server/lib/framework/kibana_framework_adapter.ts index 8327af846d1ac..7aff8352d6336 100644 --- a/x-pack/plugins/security_solution/server/lib/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/security_solution/server/lib/framework/kibana_framework_adapter.ts @@ -7,16 +7,18 @@ import { GraphQLSchema } from 'graphql'; import { runHttpQuery } from 'apollo-server-core'; import { schema as configSchema } from '@kbn/config-schema'; -import { +import type { CoreSetup, - IRouter, KibanaResponseFactory, - RequestHandlerContext, KibanaRequest, } from '../../../../../../src/core/server'; import { IndexPatternsFetcher, UI_SETTINGS } from '../../../../../../src/plugins/data/server'; import { AuthenticatedUser } from '../../../../security/common/model'; import { SetupPlugins } from '../../plugin'; +import type { + SecuritySolutionRequestHandlerContext, + SecuritySolutionPluginRouter, +} from '../../types'; import { FrameworkAdapter, @@ -27,7 +29,7 @@ import { import { buildSiemResponse } from '../detection_engine/routes/utils'; export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { - private router: IRouter; + private router: SecuritySolutionPluginRouter; private security: SetupPlugins['security']; constructor(core: CoreSetup, plugins: SetupPlugins) { @@ -125,7 +127,7 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { export function wrapRequest( request: KibanaRequest, - context: RequestHandlerContext, + context: SecuritySolutionRequestHandlerContext, user: AuthenticatedUser | null ): FrameworkRequest { return { diff --git a/x-pack/plugins/security_solution/server/lib/framework/types.ts b/x-pack/plugins/security_solution/server/lib/framework/types.ts index 1f626d9fb2dc7..b1973e15ef95b 100644 --- a/x-pack/plugins/security_solution/server/lib/framework/types.ts +++ b/x-pack/plugins/security_solution/server/lib/framework/types.ts @@ -7,9 +7,10 @@ import { IndicesGetMappingParams } from 'elasticsearch'; import { GraphQLSchema } from 'graphql'; -import { RequestHandlerContext, KibanaRequest } from '../../../../../../src/core/server'; +import { KibanaRequest } from '../../../../../../src/core/server'; import { AuthenticatedUser } from '../../../../security/common/model'; import { ESQuery } from '../../../common/typed_json'; +import type { SecuritySolutionRequestHandlerContext } from '../../types'; import { PaginationInput, PaginationInputPaginated, @@ -45,7 +46,7 @@ export interface FrameworkAdapter { export interface FrameworkRequest extends Pick { [internalFrameworkRequest]: KibanaRequest; - context: RequestHandlerContext; + context: SecuritySolutionRequestHandlerContext; user: AuthenticatedUser | null; } diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/clean_draft_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/clean_draft_timelines_route.ts index 67fc3167a4a29..2a366576608a8 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/clean_draft_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/clean_draft_timelines_route.ts @@ -5,7 +5,7 @@ */ import uuid from 'uuid'; -import { IRouter } from '../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../types'; import { ConfigType } from '../../..'; import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils'; import { TIMELINE_DRAFT_URL } from '../../../../common/constants'; @@ -18,7 +18,7 @@ import { cleanDraftTimelineSchema } from './schemas/clean_draft_timelines_schema import { TimelineType } from '../../../../common/types/timeline'; export const cleanDraftTimelinesRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, security: SetupPlugins['security'] ) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts index 77cd49406baa1..7f9a32a2275dc 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts @@ -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 { IRouter } from '../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../types'; import { TIMELINE_URL } from '../../../../common/constants'; @@ -23,7 +23,7 @@ import { createTimelines } from './utils/create_timelines'; import { DEFAULT_ERROR } from './utils/failure_cases'; export const createTimelinesRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, security: SetupPlugins['security'] ) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/export_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/export_timelines_route.ts index 38ee51fb7aa0c..f24a9234dd8fa 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/export_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/export_timelines_route.ts @@ -5,7 +5,7 @@ */ import { TIMELINE_EXPORT_URL } from '../../../../common/constants'; -import { IRouter } from '../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../types'; import { ConfigType } from '../../../config'; import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils'; @@ -19,7 +19,7 @@ import { buildFrameworkRequest } from './utils/common'; import { SetupPlugins } from '../../../plugin'; export const exportTimelinesRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, security: SetupPlugins['security'] ) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_draft_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/get_draft_timelines_route.ts index 43129f0e15f0e..59179dd33f5a3 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_draft_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/get_draft_timelines_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from '../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../types'; import { ConfigType } from '../../..'; import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils'; import { TIMELINE_DRAFT_URL } from '../../../../common/constants'; @@ -16,7 +16,7 @@ import { draftTimelineDefaults } from '../default_timeline'; import { getDraftTimelineSchema } from './schemas/get_draft_timelines_schema'; export const getDraftTimelinesRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, security: SetupPlugins['security'] ) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_timeline_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/get_timeline_route.ts index e46a644d6820e..9496513ed4cc9 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_timeline_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/get_timeline_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from '../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../types'; import { TIMELINE_URL } from '../../../../common/constants'; @@ -21,7 +21,7 @@ import { getAllTimeline } from '../saved_object'; import { TimelineStatus } from '../../../../common/types/timeline'; export const getTimelineRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, security: SetupPlugins['security'] ) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts index 811d4531b86a7..5197677864248 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts @@ -7,7 +7,7 @@ import { extname } from 'path'; import { Readable } from 'stream'; -import { IRouter } from '../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../types'; import { TIMELINE_IMPORT_URL } from '../../../../common/constants'; @@ -21,7 +21,7 @@ import { ImportTimelinesPayloadSchemaRt } from './schemas/import_timelines_schem import { buildFrameworkRequest } from './utils/common'; export const importTimelinesRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, security: SetupPlugins['security'] ) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/install_prepacked_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/install_prepacked_timelines_route.ts index aba05054abfe2..04608a821ca75 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/install_prepacked_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/install_prepacked_timelines_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from '../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../types'; import { TIMELINE_PREPACKAGED_URL } from '../../../../common/constants'; @@ -22,7 +22,7 @@ import { checkTimelineStatusRt } from './schemas/check_timelines_status_schema'; import { buildFrameworkRequest } from './utils/common'; export const installPrepackedTimelinesRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, security: SetupPlugins['security'] ) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts index 6b8ceea80c31a..2c650db0d6908 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from '../../../../../../../src/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../types'; import { TIMELINE_URL } from '../../../../common/constants'; @@ -20,7 +20,7 @@ import { createTimelines } from './utils/create_timelines'; import { CompareTimelinesStatus } from './utils/compare_timelines_status'; export const updateTimelinesRoute = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, security: SetupPlugins['security'] ) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts index c230e36e4c896..b6cbe92123adf 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts @@ -9,13 +9,14 @@ import fs from 'fs'; import { Readable } from 'stream'; import { createListStream } from '@kbn/utils'; -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { KibanaRequest } from 'src/core/server'; import { SetupPlugins } from '../../../../plugin'; +import type { SecuritySolutionRequestHandlerContext } from '../../../../types'; import { FrameworkRequest } from '../../../framework'; export const buildFrameworkRequest = async ( - context: RequestHandlerContext, + context: SecuritySolutionRequestHandlerContext, security: SetupPlugins['security'], request: KibanaRequest ): Promise => { @@ -25,7 +26,7 @@ export const buildFrameworkRequest = async ( return set( 'user', user, - set( + set( 'context.core.savedObjects.client', savedObjectsClient, request diff --git a/x-pack/plugins/security_solution/server/lib/types.ts b/x-pack/plugins/security_solution/server/lib/types.ts index 618710ebd5fc6..06ef82de5715d 100644 --- a/x-pack/plugins/security_solution/server/lib/types.ts +++ b/x-pack/plugins/security_solution/server/lib/types.ts @@ -5,8 +5,8 @@ */ import { AuthenticatedUser } from '../../../security/common/model'; -import { RequestHandlerContext } from '../../../../../src/core/server'; export { ConfigType as Configuration } from '../config'; +import type { SecuritySolutionRequestHandlerContext } from '../types'; import { FrameworkAdapter, FrameworkRequest } from './framework'; import { Hosts } from './hosts'; @@ -36,7 +36,7 @@ export interface AppBackendLibs extends AppDomainLibs { export interface SiemContext { req: FrameworkRequest; - context: RequestHandlerContext; + context: SecuritySolutionRequestHandlerContext; user: AuthenticatedUser | null; } diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 020237ad497f7..4e1521cb0f8d1 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -64,7 +64,7 @@ import { EndpointAppContextService } from './endpoint/endpoint_app_context_servi import { EndpointAppContext } from './endpoint/types'; import { registerDownloadExceptionListRoute } from './endpoint/routes/artifacts'; import { initUsageCollectors } from './usage'; -import { AppRequestContext } from './types'; +import type { SecuritySolutionRequestHandlerContext } from './types'; import { registerTrustedAppsRoutes } from './endpoint/routes/trusted_apps'; import { securitySolutionSearchStrategyProvider } from './search_strategy/security_solution'; import { securitySolutionIndexFieldsProvider } from './search_strategy/index_fields'; @@ -168,10 +168,10 @@ export class Plugin implements IPlugin => Promise.resolve(config), }; - const router = core.http.createRouter(); - core.http.registerRouteHandlerContext( + const router = core.http.createRouter(); + core.http.registerRouteHandlerContext( APP_ID, - (context, request, response): AppRequestContext => ({ + (context, request, response) => ({ getAppClient: () => this.appClientFactory.create(request), }) ); diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index 0204869904397..b02e69ffe42a0 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from '../../../../../src/core/server'; +import { SecuritySolutionPluginRouter } from '../types'; import { createRulesRoute } from '../lib/detection_engine/routes/rules/create_rules_route'; import { createIndexRoute } from '../lib/detection_engine/routes/index/create_index_route'; @@ -44,7 +44,7 @@ import { installPrepackedTimelinesRoute } from '../lib/timeline/routes/install_p import { getTimelineRoute } from '../lib/timeline/routes/get_timeline_route'; export const initRoutes = ( - router: IRouter, + router: SecuritySolutionPluginRouter, config: ConfigType, usingEphemeralEncryptionKey: boolean, security: SetupPlugins['security'], diff --git a/x-pack/plugins/security_solution/server/types.ts b/x-pack/plugins/security_solution/server/types.ts index 740e16636f1e0..c601738d981ac 100644 --- a/x-pack/plugins/security_solution/server/types.ts +++ b/x-pack/plugins/security_solution/server/types.ts @@ -3,6 +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 type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { ListsApiRequestHandlerContext } from '../../lists/server'; +import type { LicensingApiRequestHandlerContext } from '../../licensing/server'; +import type { AlertingApiRequestHandlerContext } from '../../alerts/server'; import { AppClient } from './client'; @@ -12,8 +16,11 @@ export interface AppRequestContext { getAppClient: () => AppClient; } -declare module 'src/core/server' { - interface RequestHandlerContext { - securitySolution?: AppRequestContext; - } -} +export type SecuritySolutionRequestHandlerContext = RequestHandlerContext & { + securitySolution: AppRequestContext; + licensing: LicensingApiRequestHandlerContext; + alerting: AlertingApiRequestHandlerContext; + lists?: ListsApiRequestHandlerContext; +}; + +export type SecuritySolutionPluginRouter = IRouter; diff --git a/x-pack/plugins/snapshot_restore/server/plugin.ts b/x-pack/plugins/snapshot_restore/server/plugin.ts index 4e3d743f5372c..baf39b25af4c9 100644 --- a/x-pack/plugins/snapshot_restore/server/plugin.ts +++ b/x-pack/plugins/snapshot_restore/server/plugin.ts @@ -3,12 +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. */ -declare module 'kibana/server' { - interface RequestHandlerContext { - snapshotRestore?: SnapshotRestoreContext; - } -} - import { first } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import { @@ -17,7 +11,6 @@ import { Plugin, Logger, PluginInitializerContext, - ILegacyScopedClusterClient, } from 'kibana/server'; import { PLUGIN, APP_REQUIRED_CLUSTER_PRIVILEGES } from '../common'; @@ -26,13 +19,9 @@ import { ApiRoutes } from './routes'; import { wrapEsError } from './lib'; import { isEsError } from './shared_imports'; import { elasticsearchJsPlugin } from './client/elasticsearch_sr'; -import { Dependencies } from './types'; +import type { Dependencies, SnapshotRestoreRequestHandlerContext } from './types'; import { SnapshotRestoreConfig } from './config'; -export interface SnapshotRestoreContext { - client: ILegacyScopedClusterClient; -} - async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { const [core] = await getStartServices(); const esClientConfig = { plugins: [elasticsearchJsPlugin] }; @@ -65,7 +54,7 @@ export class SnapshotRestoreServerPlugin implements Plugin return; } - const router = http.createRouter(); + const router = http.createRouter(); this.license.setup( { @@ -95,13 +84,16 @@ export class SnapshotRestoreServerPlugin implements Plugin ], }); - http.registerRouteHandlerContext('snapshotRestore', async (ctx, request) => { - this.snapshotRestoreESClient = - this.snapshotRestoreESClient ?? (await getCustomEsClient(getStartServices)); - return { - client: this.snapshotRestoreESClient.asScoped(request), - }; - }); + http.registerRouteHandlerContext( + 'snapshotRestore', + async (ctx, request) => { + this.snapshotRestoreESClient = + this.snapshotRestoreESClient ?? (await getCustomEsClient(getStartServices)); + return { + client: this.snapshotRestoreESClient.asScoped(request), + }; + } + ); this.apiRoutes.setup({ router, diff --git a/x-pack/plugins/snapshot_restore/server/services/license.ts b/x-pack/plugins/snapshot_restore/server/services/license.ts index 9b68acd073c4a..4c1478340c30f 100644 --- a/x-pack/plugins/snapshot_restore/server/services/license.ts +++ b/x-pack/plugins/snapshot_restore/server/services/license.ts @@ -4,15 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { Logger } from 'src/core/server'; -import { - KibanaRequest, - KibanaResponseFactory, - RequestHandler, - RequestHandlerContext, -} from 'kibana/server'; +import type { KibanaRequest, KibanaResponseFactory, RequestHandler } from 'kibana/server'; import { LicensingPluginSetup } from '../../../licensing/server'; import { LicenseType } from '../../../licensing/common/types'; +import type { SnapshotRestoreRequestHandlerContext } from '../types'; export interface LicenseStatus { isValid: boolean; @@ -53,11 +49,13 @@ export class License { }); } - guardApiRoute(handler: RequestHandler) { + guardApiRoute( + handler: RequestHandler + ) { const license = this; return function licenseCheck( - ctx: RequestHandlerContext, + ctx: Context, request: KibanaRequest, response: KibanaResponseFactory ) { diff --git a/x-pack/plugins/snapshot_restore/server/types.ts b/x-pack/plugins/snapshot_restore/server/types.ts index eb51f086deacc..dcef4b54e31b5 100644 --- a/x-pack/plugins/snapshot_restore/server/types.ts +++ b/x-pack/plugins/snapshot_restore/server/types.ts @@ -3,7 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { LegacyScopedClusterClient, IRouter } from 'src/core/server'; +import type { + LegacyScopedClusterClient, + ILegacyScopedClusterClient, + IRouter, + RequestHandlerContext, +} from 'src/core/server'; import { LicensingPluginSetup } from '../../licensing/server'; import { SecurityPluginSetup } from '../../security/server'; import { CloudSetup } from '../../cloud/server'; @@ -20,7 +25,7 @@ export interface Dependencies { } export interface RouteDependencies { - router: IRouter; + router: SnapshotRestoreRouter; license: License; config: { isSlmEnabled: boolean; @@ -50,3 +55,22 @@ export interface ResolveIndexResponseFromES { } export type CallAsCurrentUser = LegacyScopedClusterClient['callAsCurrentUser']; + +/** + * @internal + */ +export interface SnapshotRestoreContext { + client: ILegacyScopedClusterClient; +} + +/** + * @internal + */ +export interface SnapshotRestoreRequestHandlerContext extends RequestHandlerContext { + snapshotRestore: SnapshotRestoreContext; +} + +/** + * @internal + */ +export type SnapshotRestoreRouter = IRouter; diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index e50ab60a27738..24bf96e81ce1a 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -36,6 +36,7 @@ import { SpacesClientService, SpacesClientWrapper, } from './spaces_client'; +import type { SpacesRequestHandlerContext } from './types'; export interface PluginsSetup { features: FeaturesPluginSetup; @@ -123,7 +124,7 @@ export class Plugin { logger: this.log, }); - const externalRouter = core.http.createRouter(); + const externalRouter = core.http.createRouter(); initExternalSpacesApi({ externalRouter, log: this.log, @@ -132,7 +133,7 @@ export class Plugin { usageStatsServicePromise, }); - const internalRouter = core.http.createRouter(); + const internalRouter = core.http.createRouter(); initInternalSpacesApi({ internalRouter, getSpacesService, diff --git a/x-pack/plugins/spaces/server/routes/api/external/index.ts b/x-pack/plugins/spaces/server/routes/api/external/index.ts index d481c2e0675fa..6ece39f621801 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/index.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, IRouter, CoreSetup } from 'src/core/server'; +import { Logger, CoreSetup } from 'src/core/server'; import { initDeleteSpacesApi } from './delete'; import { initGetSpaceApi } from './get'; import { initGetAllSpacesApi } from './get_all'; @@ -14,9 +14,10 @@ import { SpacesServiceStart } from '../../../spaces_service'; import { UsageStatsServiceSetup } from '../../../usage_stats'; import { initCopyToSpacesApi } from './copy_to_space'; import { initShareToSpacesApi } from './share_to_space'; +import type { SpacesRouter } from '../../../types'; export interface ExternalRouteDeps { - externalRouter: IRouter; + externalRouter: SpacesRouter; getStartServices: CoreSetup['getStartServices']; getSpacesService: () => SpacesServiceStart; usageStatsServicePromise: Promise; diff --git a/x-pack/plugins/spaces/server/routes/api/internal/index.ts b/x-pack/plugins/spaces/server/routes/api/internal/index.ts index 675cdb548543d..574b7a25f2d10 100644 --- a/x-pack/plugins/spaces/server/routes/api/internal/index.ts +++ b/x-pack/plugins/spaces/server/routes/api/internal/index.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import type { SpacesRouter } from '../../../types'; import { SpacesServiceStart } from '../../../spaces_service/spaces_service'; import { initGetActiveSpaceApi } from './get_active_space'; export interface InternalRouteDeps { - internalRouter: IRouter; + internalRouter: SpacesRouter; getSpacesService: () => SpacesServiceStart; } diff --git a/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts b/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts index d56414a12b838..d507802011dc9 100644 --- a/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts +++ b/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts @@ -4,10 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandler } from 'kibana/server'; +import type { RequestHandler, RequestHandlerContext } from 'kibana/server'; +import type { LicensingApiRequestHandlerContext } from '../../../../licensing/server'; -export const createLicensedRouteHandler = (handler: RequestHandler) => { - const licensedRouteHandler: RequestHandler = (context, request, responseToolkit) => { +export const createLicensedRouteHandler = < + P, + Q, + B, + Context extends RequestHandlerContext & { licensing: LicensingApiRequestHandlerContext } +>( + handler: RequestHandler +) => { + const licensedRouteHandler: RequestHandler = ( + context, + request, + responseToolkit + ) => { const { license } = context.licensing; const licenseCheck = license.check('spaces', 'basic'); if (licenseCheck.state === 'unavailable' || licenseCheck.state === 'invalid') { diff --git a/x-pack/plugins/spaces/server/types.ts b/x-pack/plugins/spaces/server/types.ts new file mode 100644 index 0000000000000..c43a589ed5775 --- /dev/null +++ b/x-pack/plugins/spaces/server/types.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { LicensingApiRequestHandlerContext } from '../../licensing/server'; + +/** + * @internal + */ +export interface SpacesRequestHandlerContext extends RequestHandlerContext { + licensing: LicensingApiRequestHandlerContext; +} + +/** + * @internal + */ +export type SpacesRouter = IRouter; diff --git a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index 92965515f0876..6e949012e7ed1 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -5,8 +5,7 @@ */ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { - IRouter, +import type { SavedObjectsClientContract, ISavedObjectsRepository, IScopedClusterClient, @@ -15,6 +14,7 @@ import { UMKibanaRoute } from '../../../rest_api'; import { PluginSetupContract } from '../../../../../features/server'; import { MlPluginSetup as MlSetup } from '../../../../../ml/server'; import { UptimeESClient } from '../../lib'; +import type { UptimeRouter } from '../../../types'; export type UMElasticsearchQueryFn = ( params: { @@ -29,7 +29,7 @@ export type UMSavedObjectsQueryFn = ( ) => Promise | T; export interface UptimeCoreSetup { - router: IRouter; + router: UptimeRouter; } export interface UptimeCorePlugins { diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts index ed831f4e17368..3a6516bd33d41 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts @@ -18,7 +18,6 @@ import { AlertInstanceState, AlertInstanceContext, } from '../../../../alerts/server'; -import { IRouter } from 'kibana/server'; import { UMServerLibs } from '../lib'; import { UptimeCorePlugins, UptimeCoreSetup } from '../adapters'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; @@ -26,6 +25,7 @@ import { alertsMock, AlertServicesMock } from '../../../../alerts/server/mocks'; import { GetMonitorStatusResult } from '../requests/get_monitor_status'; import { makePing } from '../../../common/runtime_types/ping'; import { GetMonitorAvailabilityResult } from '../requests/get_monitor_availability'; +import type { UptimeRouter } from '../../types'; /** * The alert takes some dependencies as parameters; these are things like @@ -35,7 +35,7 @@ import { GetMonitorAvailabilityResult } from '../requests/get_monitor_availabili * so we don't have to mock them all for each test. */ const bootstrapDependencies = (customRequests?: any) => { - const router: IRouter = {} as IRouter; + const router = {} as UptimeRouter; // these server/libs parameters don't have any functionality, which is fine // because we aren't testing them here const server: UptimeCoreSetup = { router }; diff --git a/x-pack/plugins/uptime/server/rest_api/types.ts b/x-pack/plugins/uptime/server/rest_api/types.ts index 4e627cebb3459..3d3f2393d242f 100644 --- a/x-pack/plugins/uptime/server/rest_api/types.ts +++ b/x-pack/plugins/uptime/server/rest_api/types.ts @@ -10,12 +10,12 @@ import { RouteConfig, RouteMethod, SavedObjectsClientContract, - RequestHandlerContext, KibanaRequest, KibanaResponseFactory, IKibanaResponse, } from 'kibana/server'; import { UMServerLibs, UptimeESClient } from '../lib/lib'; +import type { UptimeRequestHandlerContext } from '../types'; /** * Defines the basic properties employed by Uptime routes. @@ -38,7 +38,9 @@ export type UMRouteDefinition = UMServerRoute & * provided by the Kibana platform. Route objects must conform to this type in order * to successfully interact with the Kibana platform. */ -export type UMKibanaRoute = UMRouteDefinition>; +export type UMKibanaRoute = UMRouteDefinition< + RequestHandler +>; /** * This is an abstraction over the default Kibana route type. This allows us to use custom @@ -68,7 +70,7 @@ export type UMRouteHandler = ({ savedObjectsClient, }: { uptimeEsClient: UptimeESClient; - context: RequestHandlerContext; + context: UptimeRequestHandlerContext; request: KibanaRequest, Record, Record>; response: KibanaResponseFactory; savedObjectsClient: SavedObjectsClientContract; diff --git a/x-pack/plugins/uptime/server/types.ts b/x-pack/plugins/uptime/server/types.ts new file mode 100644 index 0000000000000..7a107268b2cfd --- /dev/null +++ b/x-pack/plugins/uptime/server/types.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 type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { AlertingApiRequestHandlerContext } from '../../alerts/server'; +import type { LicensingApiRequestHandlerContext } from '../../licensing/server'; +/** + * @internal + */ +export interface UptimeRequestHandlerContext extends RequestHandlerContext { + licensing: LicensingApiRequestHandlerContext; + alerting: AlertingApiRequestHandlerContext; +} + +/** + * @internal + */ +export type UptimeRouter = IRouter; diff --git a/x-pack/plugins/watcher/server/index.ts b/x-pack/plugins/watcher/server/index.ts index 356be781fb194..51eb7bfa543fe 100644 --- a/x-pack/plugins/watcher/server/index.ts +++ b/x-pack/plugins/watcher/server/index.ts @@ -6,6 +6,4 @@ import { PluginInitializerContext } from 'kibana/server'; import { WatcherServerPlugin } from './plugin'; -export { WatcherContext } from './plugin'; - export const plugin = (ctx: PluginInitializerContext) => new WatcherServerPlugin(ctx); diff --git a/x-pack/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts b/x-pack/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts index 1b2476fc78b45..0d162f300d371 100644 --- a/x-pack/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts +++ b/x-pack/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts @@ -4,20 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - KibanaRequest, - KibanaResponseFactory, - RequestHandler, - RequestHandlerContext, -} from 'kibana/server'; -import { RouteDependencies } from '../../types'; +import type { KibanaRequest, KibanaResponseFactory, RequestHandler } from 'kibana/server'; +import type { RouteDependencies, WatcherRequestHandlerContext } from '../../types'; -export const licensePreRoutingFactory = ( +export const licensePreRoutingFactory = ( { getLicenseStatus }: RouteDependencies, - handler: RequestHandler + handler: RequestHandler ) => { return function licenseCheck( - ctx: RequestHandlerContext, + ctx: Context, request: KibanaRequest, response: KibanaResponseFactory ) { diff --git a/x-pack/plugins/watcher/server/plugin.ts b/x-pack/plugins/watcher/server/plugin.ts index 9ff46283a72a6..8ce63ab009779 100644 --- a/x-pack/plugins/watcher/server/plugin.ts +++ b/x-pack/plugins/watcher/server/plugin.ts @@ -3,23 +3,20 @@ * 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 'kibana/server' { - interface RequestHandlerContext { - watcher?: WatcherContext; - } -} - import { CoreSetup, ILegacyCustomClusterClient, - ILegacyScopedClusterClient, Logger, Plugin, PluginInitializerContext, } from 'kibana/server'; import { PLUGIN, INDEX_NAMES } from '../common/constants'; -import { Dependencies, LicenseStatus, RouteDependencies } from './types'; +import type { + Dependencies, + LicenseStatus, + RouteDependencies, + WatcherRequestHandlerContext, +} from './types'; import { registerSettingsRoutes } from './routes/api/settings'; import { registerIndicesRoutes } from './routes/api/indices'; @@ -30,10 +27,6 @@ import { registerListFieldsRoute } from './routes/api/register_list_fields_route import { registerLoadHistoryRoute } from './routes/api/register_load_history_route'; import { elasticsearchJsPlugin } from './lib/elasticsearch_js_plugin'; -export interface WatcherContext { - client: ILegacyScopedClusterClient; -} - async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { const [core] = await getStartServices(); const esConfig = { plugins: [elasticsearchJsPlugin] }; @@ -53,7 +46,7 @@ export class WatcherServerPlugin implements Plugin { } async setup({ http, getStartServices }: CoreSetup, { licensing, features }: Dependencies) { - const router = http.createRouter(); + const router = http.createRouter(); const routeDependencies: RouteDependencies = { router, getLicenseStatus: () => this.licenseStatus, @@ -85,12 +78,15 @@ export class WatcherServerPlugin implements Plugin { ], }); - http.registerRouteHandlerContext('watcher', async (ctx, request) => { - this.watcherESClient = this.watcherESClient ?? (await getCustomEsClient(getStartServices)); - return { - client: this.watcherESClient.asScoped(request), - }; - }); + http.registerRouteHandlerContext( + 'watcher', + async (ctx, request) => { + this.watcherESClient = this.watcherESClient ?? (await getCustomEsClient(getStartServices)); + return { + client: this.watcherESClient.asScoped(request), + }; + } + ); registerListFieldsRoute(routeDependencies); registerLoadHistoryRoute(routeDependencies); diff --git a/x-pack/plugins/watcher/server/types.ts b/x-pack/plugins/watcher/server/types.ts index 5ef3aef7de1c6..db31e94bf77ed 100644 --- a/x-pack/plugins/watcher/server/types.ts +++ b/x-pack/plugins/watcher/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; +import type { ILegacyScopedClusterClient, IRouter, RequestHandlerContext } from 'src/core/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { LicensingPluginSetup } from '../../licensing/server'; @@ -21,7 +21,7 @@ export interface ServerShim { } export interface RouteDependencies { - router: IRouter; + router: WatcherRouter; getLicenseStatus: () => LicenseStatus; } @@ -29,3 +29,22 @@ export interface LicenseStatus { hasRequired: boolean; message?: string; } + +/** + * @internal + */ +export interface WatcherContext { + client: ILegacyScopedClusterClient; +} + +/** + * @internal + */ +export interface WatcherRequestHandlerContext extends RequestHandlerContext { + watcher: WatcherContext; +} + +/** + * @internal + */ +export type WatcherRouter = IRouter; From 477d0bbe2102c0654a0c3ac7f5dca148a042bc50 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Thu, 21 Jan 2021 15:27:28 +0100 Subject: [PATCH 53/83] add SavedObject export hooks (#87807) * initial POC * fix spaces UT * address POC feedback, add tests for applyExportTransforms * add sorting for transforms * add type validation in SOTR * add FTR tests * update documentation * add explicit so type export for client-side * update generated doc * add exporter test * update license headers * update generated doc * fix so import... imports * update generated doc * nits * update generated doc * rename test plugins * adding FTR tests on export failures --- .../core/server/kibana-plugin-core-server.md | 2 + ...ore-server.savedobjectexportbaseoptions.md | 1 + ...er.savedobjectexportbaseoptions.request.md | 13 + ...rver.savedobjectsexporter._constructor_.md | 5 +- ...plugin-core-server.savedobjectsexporter.md | 2 +- ...bjectsexporterror.invalidtransformerror.md | 24 + ...gin-core-server.savedobjectsexporterror.md | 2 + ...objectsexporterror.objecttransformerror.md | 25 + ...core-server.savedobjectsexporttransform.md | 86 +++ ...rver.savedobjectsexporttransformcontext.md | 20 + ...edobjectsexporttransformcontext.request.md | 13 + ...er.savedobjectstypemanagementdefinition.md | 1 + ...bjectstypemanagementdefinition.onexport.md | 22 + ...bjectstypemanagementdefinition.onimport.md | 5 +- src/core/public/public.api.md | 7 + src/core/server/index.ts | 2 + .../export/apply_export_transforms.test.ts | 300 +++++++++++ .../export/apply_export_transforms.ts | 91 ++++ .../server/saved_objects/export/errors.ts | 28 + src/core/server/saved_objects/export/index.ts | 2 + .../export/saved_objects_exporter.test.ts | 73 ++- .../export/saved_objects_exporter.ts | 34 +- src/core/server/saved_objects/export/types.ts | 94 +++- .../server/saved_objects/export/utils.test.ts | 57 ++ src/core/server/saved_objects/export/utils.ts | 46 ++ src/core/server/saved_objects/index.ts | 2 + .../server/saved_objects/routes/export.ts | 11 +- .../saved_objects/saved_objects_service.ts | 1 + .../saved_objects_type_registry.test.ts | 77 ++- .../saved_objects_type_registry.ts | 11 + src/core/server/saved_objects/types.ts | 15 +- src/core/server/server.api.md | 15 +- src/core/server/types.ts | 29 +- src/plugins/data/public/public.api.md | 8 + src/plugins/embeddable/public/public.api.md | 8 + .../export_transform/data.json | 149 ++++++ .../export_transform/mappings.json | 499 ++++++++++++++++++ .../kibana.json | 8 + .../package.json | 14 + .../server/index.ts | 11 + .../server/plugin.ts | 141 +++++ .../tsconfig.json | 16 + .../kibana.json | 4 +- .../package.json | 4 +- .../server/index.ts | 4 +- .../server/plugin.ts | 2 +- .../tsconfig.json | 0 .../export_transform.ts | 140 +++++ .../saved_objects_management/index.ts | 1 + .../lib/copy_to_spaces/copy_to_spaces.test.ts | 1 + .../lib/copy_to_spaces/copy_to_spaces.ts | 1 + .../resolve_copy_conflicts.test.ts | 1 + .../copy_to_spaces/resolve_copy_conflicts.ts | 1 + 53 files changed, 2088 insertions(+), 41 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.request.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md create mode 100644 src/core/server/saved_objects/export/apply_export_transforms.test.ts create mode 100644 src/core/server/saved_objects/export/apply_export_transforms.ts create mode 100644 src/core/server/saved_objects/export/utils.test.ts create mode 100644 src/core/server/saved_objects/export/utils.ts create mode 100644 test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json create mode 100644 test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json create mode 100644 test/plugin_functional/plugins/saved_object_export_transforms/kibana.json create mode 100644 test/plugin_functional/plugins/saved_object_export_transforms/package.json create mode 100644 test/plugin_functional/plugins/saved_object_export_transforms/server/index.ts create mode 100644 test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts create mode 100644 test/plugin_functional/plugins/saved_object_export_transforms/tsconfig.json rename test/plugin_functional/plugins/{saved_object_hooks => saved_object_import_warnings}/kibana.json (50%) rename test/plugin_functional/plugins/{saved_object_hooks => saved_object_import_warnings}/package.json (68%) rename test/plugin_functional/plugins/{saved_object_hooks => saved_object_import_warnings}/server/index.ts (73%) rename test/plugin_functional/plugins/{saved_object_hooks => saved_object_import_warnings}/server/plugin.ts (96%) rename test/plugin_functional/plugins/{saved_object_hooks => saved_object_import_warnings}/tsconfig.json (100%) create mode 100644 test/plugin_functional/test_suites/saved_objects_management/export_transform.ts diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 4c6116540c12d..82f4a285409c9 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -164,6 +164,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsExportByObjectOptions](./kibana-plugin-core-server.savedobjectsexportbyobjectoptions.md) | Options for the [export by objects API](./kibana-plugin-core-server.savedobjectsexporter.exportbyobjects.md) | | [SavedObjectsExportByTypeOptions](./kibana-plugin-core-server.savedobjectsexportbytypeoptions.md) | Options for the [export by type API](./kibana-plugin-core-server.savedobjectsexporter.exportbytypes.md) | | [SavedObjectsExportResultDetails](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | +| [SavedObjectsExportTransformContext](./kibana-plugin-core-server.savedobjectsexporttransformcontext.md) | Context passed down to a [export transform function](./kibana-plugin-core-server.savedobjectsexporttransform.md) | | [SavedObjectsFindOptions](./kibana-plugin-core-server.savedobjectsfindoptions.md) | | | [SavedObjectsFindOptionsReference](./kibana-plugin-core-server.savedobjectsfindoptionsreference.md) | | | [SavedObjectsFindResponse](./kibana-plugin-core-server.savedobjectsfindresponse.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | @@ -299,6 +300,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | | [SavedObjectsClientFactoryProvider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md). | | [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | +| [SavedObjectsExportTransform](./kibana-plugin-core-server.savedobjectsexporttransform.md) | Transformation function used to mutate the exported objects of the associated type.A type's export transform function will be executed once per user-initiated export, for all objects of that type. | | [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation | | [SavedObjectsImportHook](./kibana-plugin-core-server.savedobjectsimporthook.md) | A hook associated with a specific saved object type, that will be invoked during the import process. The hook will have access to the objects of the registered type.Currently, the only supported feature for import hooks is to return warnings to be displayed in the UI when the import succeeds. The only interactions the hook can have with the import process is via the hook's response. Mutating the objects inside the hook's code will have no effect. | | [SavedObjectsImportWarning](./kibana-plugin-core-server.savedobjectsimportwarning.md) | Composite type of all the possible types of import warnings.See [SavedObjectsImportSimpleWarning](./kibana-plugin-core-server.savedobjectsimportsimplewarning.md) and [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) for more details. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.md index eb35bb6a4ea5c..0e8fa73039d40 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.md @@ -18,4 +18,5 @@ export interface SavedObjectExportBaseOptions | [excludeExportDetails](./kibana-plugin-core-server.savedobjectexportbaseoptions.excludeexportdetails.md) | boolean | flag to not append [export details](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) to the end of the export stream. | | [includeReferencesDeep](./kibana-plugin-core-server.savedobjectexportbaseoptions.includereferencesdeep.md) | boolean | flag to also include all related saved objects in the export stream. | | [namespace](./kibana-plugin-core-server.savedobjectexportbaseoptions.namespace.md) | string | optional namespace to override the namespace used by the savedObjectsClient. | +| [request](./kibana-plugin-core-server.savedobjectexportbaseoptions.request.md) | KibanaRequest | The http request initiating the export. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.request.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.request.md new file mode 100644 index 0000000000000..d425f9b88e818 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.request.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectExportBaseOptions](./kibana-plugin-core-server.savedobjectexportbaseoptions.md) > [request](./kibana-plugin-core-server.savedobjectexportbaseoptions.request.md) + +## SavedObjectExportBaseOptions.request property + +The http request initiating the export. + +Signature: + +```typescript +request: KibanaRequest; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter._constructor_.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter._constructor_.md index cc192b03ca7c2..5e959bbee7beb 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter._constructor_.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter._constructor_.md @@ -9,8 +9,9 @@ Constructs a new instance of the `SavedObjectsExporter` class Signature: ```typescript -constructor({ savedObjectsClient, exportSizeLimit, }: { +constructor({ savedObjectsClient, typeRegistry, exportSizeLimit, }: { savedObjectsClient: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; exportSizeLimit: number; }); ``` @@ -19,5 +20,5 @@ constructor({ savedObjectsClient, exportSizeLimit, }: { | Parameter | Type | Description | | --- | --- | --- | -| { savedObjectsClient, exportSizeLimit, } | {
savedObjectsClient: SavedObjectsClientContract;
exportSizeLimit: number;
} | | +| { savedObjectsClient, typeRegistry, exportSizeLimit, } | {
savedObjectsClient: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
exportSizeLimit: number;
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter.md index d8d9248f34af6..727108b824c84 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter.md @@ -15,7 +15,7 @@ export declare class SavedObjectsExporter | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)({ savedObjectsClient, exportSizeLimit, })](./kibana-plugin-core-server.savedobjectsexporter._constructor_.md) | | Constructs a new instance of the SavedObjectsExporter class | +| [(constructor)({ savedObjectsClient, typeRegistry, exportSizeLimit, })](./kibana-plugin-core-server.savedobjectsexporter._constructor_.md) | | Constructs a new instance of the SavedObjectsExporter class | ## Properties diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md new file mode 100644 index 0000000000000..5a390bd450421 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportError](./kibana-plugin-core-server.savedobjectsexporterror.md) > [invalidTransformError](./kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md) + +## SavedObjectsExportError.invalidTransformError() method + +Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) performed an invalid operation during the transform, such as removing objects from the export, or changing an object's type or id. + +Signature: + +```typescript +static invalidTransformError(objectKeys: string[]): SavedObjectsExportError; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objectKeys | string[] | | + +Returns: + +`SavedObjectsExportError` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.md index bfeaa03a94700..7d5c6e5d89a5b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.md @@ -29,5 +29,7 @@ export declare class SavedObjectsExportError extends Error | Method | Modifiers | Description | | --- | --- | --- | | [exportSizeExceeded(limit)](./kibana-plugin-core-server.savedobjectsexporterror.exportsizeexceeded.md) | static | | +| [invalidTransformError(objectKeys)](./kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md) | static | Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) performed an invalid operation during the transform, such as removing objects from the export, or changing an object's type or id. | | [objectFetchError(objects)](./kibana-plugin-core-server.savedobjectsexporterror.objectfetcherror.md) | static | | +| [objectTransformError(objects, cause)](./kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md) | static | Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) threw an error | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md new file mode 100644 index 0000000000000..4463e9ff06da0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportError](./kibana-plugin-core-server.savedobjectsexporterror.md) > [objectTransformError](./kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md) + +## SavedObjectsExportError.objectTransformError() method + +Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) threw an error + +Signature: + +```typescript +static objectTransformError(objects: SavedObject[], cause: Error): SavedObjectsExportError; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | SavedObject[] | | +| cause | Error | | + +Returns: + +`SavedObjectsExportError` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md new file mode 100644 index 0000000000000..50d4c5425e8fd --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md @@ -0,0 +1,86 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportTransform](./kibana-plugin-core-server.savedobjectsexporttransform.md) + +## SavedObjectsExportTransform type + +Transformation function used to mutate the exported objects of the associated type. + +A type's export transform function will be executed once per user-initiated export, for all objects of that type. + +Signature: + +```typescript +export declare type SavedObjectsExportTransform = (context: SavedObjectsExportTransformContext, objects: Array>) => SavedObject[] | Promise; +``` + +## Remarks + +Trying to change an object's id or type during the transform will result in a runtime error during the export process. + +## Example 1 + +Registering a transform function changing the object's attributes during the export + +```ts +// src/plugins/my_plugin/server/plugin.ts +import { myType } from './saved_objects'; + +export class Plugin() { + setup: (core: CoreSetup) => { + core.savedObjects.registerType({ + ...myType, + management: { + ...myType.management, + onExport: (ctx, objects) => { + return objects.map((obj) => ({ + ...obj, + attributes: { + ...obj.attributes, + enabled: false, + } + }) + } + }, + }); + } +} + +``` + +## Example 2 + +Registering a transform function adding additional objects to the export + +```ts +// src/plugins/my_plugin/server/plugin.ts +import { myType } from './saved_objects'; + +export class Plugin() { + setup: (core: CoreSetup) => { + const savedObjectStartContractPromise = getStartServices().then( + ([{ savedObjects: savedObjectsStart }]) => savedObjectsStart + ); + + core.savedObjects.registerType({ + ...myType, + management: { + ...myType.management, + onExport: async (ctx, objects) => { + const { getScopedClient } = await savedObjectStartContractPromise; + const client = getScopedClient(ctx.request); + + const depResponse = await client.find({ + type: 'my-nested-object', + hasReference: objs.map(({ id, type }) => ({ id, type })), + }); + + return [...objs, ...depResponse.saved_objects]; + } + }, + }); + } +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.md new file mode 100644 index 0000000000000..271f0048842b2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportTransformContext](./kibana-plugin-core-server.savedobjectsexporttransformcontext.md) + +## SavedObjectsExportTransformContext interface + +Context passed down to a [export transform function](./kibana-plugin-core-server.savedobjectsexporttransform.md) + +Signature: + +```typescript +export interface SavedObjectsExportTransformContext +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [request](./kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md) | KibanaRequest | The request that initiated the export request. Can be used to create scoped services or client inside the [transformation](./kibana-plugin-core-server.savedobjectsexporttransform.md) | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md new file mode 100644 index 0000000000000..fe04698899c7c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportTransformContext](./kibana-plugin-core-server.savedobjectsexporttransformcontext.md) > [request](./kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md) + +## SavedObjectsExportTransformContext.request property + +The request that initiated the export request. Can be used to create scoped services or client inside the [transformation](./kibana-plugin-core-server.savedobjectsexporttransform.md) + +Signature: + +```typescript +request: KibanaRequest; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md index 92b6ddf29b8ec..e9cc2b12108d6 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md @@ -22,5 +22,6 @@ export interface SavedObjectsTypeManagementDefinition | [getTitle](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md) | (savedObject: SavedObject<any>) => string | Function returning the title to display in the management table. If not defined, will use the object's type and id to generate a label. | | [icon](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.icon.md) | string | The eui icon name to display in the management table. If not defined, the default icon will be used. | | [importableAndExportable](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.importableandexportable.md) | boolean | Is the type importable or exportable. Defaults to false. | +| [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) | SavedObjectsExportTransform | An optional export transform function that can be used transform the objects of the registered type during the export process.It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list.See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. | | [onImport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md) | SavedObjectsImportHook | An optional [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) to use when importing given type.Import hooks are executed during the savedObjects import process and allow to interact with the imported objects. See the [hook documentation](./kibana-plugin-core-server.savedobjectsimporthook.md) for more info. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md new file mode 100644 index 0000000000000..6302b36a73c68 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsTypeManagementDefinition](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) > [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) + +## SavedObjectsTypeManagementDefinition.onExport property + +An optional export transform function that can be used transform the objects of the registered type during the export process. + +It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list. + +See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. + +Signature: + +```typescript +onExport?: SavedObjectsExportTransform; +``` + +## Remarks + +`importableAndExportable` must be `true` to specify this property. + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md index 55733ca5d4443..f6634c01c66ba 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md @@ -14,6 +14,10 @@ Import hooks are executed during the savedObjects import process and allow to in onImport?: SavedObjectsImportHook; ``` +## Remarks + +`importableAndExportable` must be `true` to specify this property. + ## Example Registering a hook displaying a warning about a specific type of object @@ -48,5 +52,4 @@ export class Plugin() { } ``` - messages returned in the warnings are user facing and must be translated. diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 0a166d4511c5f..52fc8fbf33910 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -9,6 +9,7 @@ import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import Boom from '@hapi/boom'; import { ConfigDeprecationProvider } from '@kbn/config'; import { ConfigPath } from '@kbn/config'; +import { DetailedPeerCertificate } from 'tls'; import { EnvironmentMode } from '@kbn/config'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; @@ -18,20 +19,25 @@ import { EuiGlobalToastListToast } from '@elastic/eui'; import { History } from 'history'; import { Href } from 'history'; import { IconType } from '@elastic/eui'; +import { IncomingHttpHeaders } from 'http'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; import { Logger } from '@kbn/logging'; import { LogMeta } from '@kbn/logging'; import { MaybePromise } from '@kbn/utility-types'; +import { ObjectType } from '@kbn/config-schema'; import { Observable } from 'rxjs'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; +import { PeerCertificate } from 'tls'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/server/types'; import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; +import { Request } from '@hapi/hapi'; import * as Rx from 'rxjs'; +import { SchemaTypeError } from '@kbn/config-schema'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; @@ -39,6 +45,7 @@ import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UiCounterMetricType } from '@kbn/analytics'; import { UnregisterCallback } from 'history'; +import { URL } from 'url'; import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; // @internal (undocumented) diff --git a/src/core/server/index.ts b/src/core/server/index.ts index a27863a458f2b..af6d511a58779 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -320,6 +320,8 @@ export { SavedObjectsExportByObjectOptions, SavedObjectsExportByTypeOptions, SavedObjectsExportError, + SavedObjectsExportTransform, + SavedObjectsExportTransformContext, SavedObjectsImporter, ISavedObjectsImporter, SavedObjectsImportError, diff --git a/src/core/server/saved_objects/export/apply_export_transforms.test.ts b/src/core/server/saved_objects/export/apply_export_transforms.test.ts new file mode 100644 index 0000000000000..b1d0a35162524 --- /dev/null +++ b/src/core/server/saved_objects/export/apply_export_transforms.test.ts @@ -0,0 +1,300 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { SavedObject } from '../../../types'; +import { KibanaRequest } from '../../http'; +import { httpServerMock } from '../../http/http_server.mocks'; +import { applyExportTransforms } from './apply_export_transforms'; +import { SavedObjectsExportTransform } from './types'; + +const createObj = ( + type: string, + id: string, + attributes: Record = {} +): SavedObject => ({ + type, + id, + attributes, + references: [], +}); + +const createTransform = ( + implementation: SavedObjectsExportTransform = (ctx, objs) => objs +): jest.MockedFunction => jest.fn(implementation); + +const expectedContext = { + request: expect.any(KibanaRequest), +}; + +describe('applyExportTransforms', () => { + let request: ReturnType; + + beforeEach(() => { + request = httpServerMock.createKibanaRequest(); + }); + + it('calls the transform functions with the correct parameters', async () => { + const foo1 = createObj('foo', '1'); + const foo2 = createObj('foo', '2'); + const bar1 = createObj('bar', '1'); + + const fooTransform = createTransform(); + const barTransform = createTransform(); + + await applyExportTransforms({ + request, + objects: [foo1, bar1, foo2], + transforms: { + foo: fooTransform, + bar: barTransform, + }, + }); + + expect(fooTransform).toHaveBeenCalledTimes(1); + expect(fooTransform).toHaveBeenCalledWith(expectedContext, [foo1, foo2]); + + expect(barTransform).toHaveBeenCalledTimes(1); + expect(barTransform).toHaveBeenCalledWith(expectedContext, [bar1]); + }); + + it('does not call the transform functions if no objects are present', async () => { + const foo1 = createObj('foo', '1'); + + const fooTransform = createTransform(); + const barTransform = createTransform(); + + await applyExportTransforms({ + request, + objects: [foo1], + transforms: { + foo: fooTransform, + bar: barTransform, + }, + }); + + expect(fooTransform).toHaveBeenCalledTimes(1); + expect(fooTransform).toHaveBeenCalledWith(expectedContext, [foo1]); + + expect(barTransform).not.toHaveBeenCalled(); + }); + + it('allows to add objects to the export', async () => { + const foo1 = createObj('foo', '1'); + const foo2 = createObj('foo', '2'); + const bar1 = createObj('bar', '1'); + const dolly1 = createObj('dolly', '1'); + const hello1 = createObj('hello', '1'); + + const fooTransform = createTransform((ctx, objs) => { + return [...objs, dolly1]; + }); + const barTransform = createTransform((ctx, objs) => { + return [...objs, hello1]; + }); + + const result = await applyExportTransforms({ + request, + objects: [foo1, bar1, foo2], + transforms: { + foo: fooTransform, + bar: barTransform, + }, + }); + + expect(result).toEqual([foo1, foo2, dolly1, bar1, hello1]); + }); + + it('returns unmutated objects if no transform is defined for the type', async () => { + const foo1 = createObj('foo', '1'); + const foo2 = createObj('foo', '2'); + const bar1 = createObj('bar', '1'); + const bar2 = createObj('bar', '2'); + const dolly1 = createObj('dolly', '1'); + + const fooTransform = createTransform((ctx, objs) => { + return [...objs, dolly1]; + }); + + const result = await applyExportTransforms({ + request, + objects: [foo1, foo2, bar1, bar2], + transforms: { + foo: fooTransform, + }, + }); + + expect(result).toEqual([foo1, foo2, dolly1, bar1, bar2]); + }); + + it('allows to mutate objects', async () => { + const foo1 = createObj('foo', '1', { enabled: true }); + const foo2 = createObj('foo', '2', { enabled: true }); + + const disableFoo = (obj: SavedObject) => ({ + ...obj, + attributes: { + ...obj.attributes, + enabled: false, + }, + }); + + const fooTransform = createTransform((ctx, objs) => { + return objs.map(disableFoo); + }); + + const result = await applyExportTransforms({ + request, + objects: [foo1, foo2], + transforms: { + foo: fooTransform, + }, + }); + + expect(result).toEqual([foo1, foo2].map(disableFoo)); + }); + + it('supports async transforms', async () => { + const foo1 = createObj('foo', '1'); + const bar1 = createObj('bar', '1'); + const dolly1 = createObj('dolly', '1'); + const hello1 = createObj('hello', '1'); + + const fooTransform = createTransform((ctx, objs) => { + return Promise.resolve([...objs, dolly1]); + }); + + const barTransform = createTransform((ctx, objs) => { + return [...objs, hello1]; + }); + + const result = await applyExportTransforms({ + request, + objects: [foo1, bar1], + transforms: { + foo: fooTransform, + bar: barTransform, + }, + }); + + expect(result).toEqual([foo1, dolly1, bar1, hello1]); + }); + + it('uses the provided sortFunction when provided', async () => { + const foo1 = createObj('foo', 'A'); + const bar1 = createObj('bar', 'B'); + const dolly1 = createObj('dolly', 'C'); + const hello1 = createObj('hello', 'D'); + + const fooTransform = createTransform((ctx, objs) => { + return [...objs, dolly1]; + }); + + const barTransform = createTransform((ctx, objs) => { + return [...objs, hello1]; + }); + + const result = await applyExportTransforms({ + request, + objects: [foo1, bar1], + transforms: { + foo: fooTransform, + bar: barTransform, + }, + sortFunction: (obj1, obj2) => (obj1.id > obj2.id ? 1 : -1), + }); + + expect(result).toEqual([foo1, bar1, dolly1, hello1]); + }); + + it('throws when removing objects', async () => { + const foo1 = createObj('foo', '1', { enabled: true }); + const foo2 = createObj('foo', '2', { enabled: true }); + + const fooTransform = createTransform((ctx, objs) => { + return [objs[0]]; + }); + + await expect( + applyExportTransforms({ + request, + objects: [foo1, foo2], + transforms: { + foo: fooTransform, + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid transform performed on objects to export"` + ); + }); + + it('throws when changing the object type', async () => { + const foo1 = createObj('foo', '1', { enabled: true }); + const foo2 = createObj('foo', '2', { enabled: true }); + + const fooTransform = createTransform((ctx, objs) => { + return objs.map((obj) => ({ + ...obj, + type: 'mutated', + })); + }); + + await expect( + applyExportTransforms({ + request, + objects: [foo1, foo2], + transforms: { + foo: fooTransform, + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid transform performed on objects to export"` + ); + }); + + it('throws when changing the object id', async () => { + const foo1 = createObj('foo', '1', { enabled: true }); + const foo2 = createObj('foo', '2', { enabled: true }); + + const fooTransform = createTransform((ctx, objs) => { + return objs.map((obj, idx) => ({ + ...obj, + id: `mutated-${idx}`, + })); + }); + + await expect( + applyExportTransforms({ + request, + objects: [foo1, foo2], + transforms: { + foo: fooTransform, + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid transform performed on objects to export"` + ); + }); + + it('throws if the transform function throws', async () => { + const foo1 = createObj('foo', '1'); + + const fooTransform = createTransform(() => { + throw new Error('oups.'); + }); + + await expect( + applyExportTransforms({ + request, + objects: [foo1], + transforms: { + foo: fooTransform, + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Error transforming objects to export"`); + }); +}); diff --git a/src/core/server/saved_objects/export/apply_export_transforms.ts b/src/core/server/saved_objects/export/apply_export_transforms.ts new file mode 100644 index 0000000000000..0297fe201ef61 --- /dev/null +++ b/src/core/server/saved_objects/export/apply_export_transforms.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { SavedObject } from '../../../types'; +import { KibanaRequest } from '../../http'; +import { SavedObjectsExportError } from './errors'; +import { SavedObjectsExportTransform, SavedObjectsExportTransformContext } from './types'; +import { getObjKey, SavedObjectComparator } from './utils'; + +interface ApplyExportTransformsOptions { + objects: SavedObject[]; + request: KibanaRequest; + transforms: Record; + sortFunction?: SavedObjectComparator; +} + +export const applyExportTransforms = async ({ + objects, + request, + transforms, + sortFunction, +}: ApplyExportTransformsOptions): Promise => { + const context = createContext(request); + const byType = splitByType(objects); + + let finalObjects: SavedObject[] = []; + for (const [type, typeObjs] of Object.entries(byType)) { + const typeTransformFn = transforms[type]; + if (typeTransformFn) { + finalObjects = [ + ...finalObjects, + ...(await applyTransform(typeObjs, typeTransformFn, context)), + ]; + } else { + finalObjects = [...finalObjects, ...typeObjs]; + } + } + + if (sortFunction) { + finalObjects.sort(sortFunction); + } + + return finalObjects; +}; + +const applyTransform = async ( + objs: SavedObject[], + transformFn: SavedObjectsExportTransform, + context: SavedObjectsExportTransformContext +) => { + const objKeys = objs.map(getObjKey); + let transformedObjects: SavedObject[]; + try { + transformedObjects = await transformFn(context, objs); + } catch (e) { + throw SavedObjectsExportError.objectTransformError(objs, e); + } + assertValidTransform(transformedObjects, objKeys); + return transformedObjects; +}; + +const createContext = (request: KibanaRequest): SavedObjectsExportTransformContext => { + return { + request, + }; +}; + +const splitByType = (objects: SavedObject[]): Record => { + return objects.reduce((memo, obj) => { + memo[obj.type] = [...(memo[obj.type] ?? []), obj]; + return memo; + }, {} as Record); +}; + +const assertValidTransform = (transformedObjects: SavedObject[], initialKeys: string[]) => { + const transformedKeys = transformedObjects.map(getObjKey); + const missingKeys: string[] = []; + initialKeys.forEach((initialKey) => { + if (!transformedKeys.includes(initialKey)) { + missingKeys.push(initialKey); + } + }); + if (missingKeys.length) { + throw SavedObjectsExportError.invalidTransformError(missingKeys); + } +}; diff --git a/src/core/server/saved_objects/export/errors.ts b/src/core/server/saved_objects/export/errors.ts index 96a729846f6b5..5720f3b2daf3e 100644 --- a/src/core/server/saved_objects/export/errors.ts +++ b/src/core/server/saved_objects/export/errors.ts @@ -36,4 +36,32 @@ export class SavedObjectsExportError extends Error { objects, }); } + + /** + * Error returned when a {@link SavedObjectsExportTransform | export tranform} threw an error + */ + static objectTransformError(objects: SavedObject[], cause: Error) { + return new SavedObjectsExportError( + 'object-transform-error', + 'Error transforming objects to export', + { + objects, + cause: cause.message, + } + ); + } + + /** + * Error returned when a {@link SavedObjectsExportTransform | export tranform} performed an invalid operation + * during the transform, such as removing objects from the export, or changing an object's type or id. + */ + static invalidTransformError(objectKeys: string[]) { + return new SavedObjectsExportError( + 'invalid-transform-error', + 'Invalid transform performed on objects to export', + { + objectKeys, + } + ); + } } diff --git a/src/core/server/saved_objects/export/index.ts b/src/core/server/saved_objects/export/index.ts index 386b8c208ad6d..8ac2e68819c46 100644 --- a/src/core/server/saved_objects/export/index.ts +++ b/src/core/server/saved_objects/export/index.ts @@ -11,6 +11,8 @@ export { SavedObjectExportBaseOptions, SavedObjectsExportByTypeOptions, SavedObjectsExportResultDetails, + SavedObjectsExportTransformContext, + SavedObjectsExportTransform, } from './types'; export { ISavedObjectsExporter, SavedObjectsExporter } from './saved_objects_exporter'; export { SavedObjectsExportError } from './errors'; diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts index e7d37280762fd..346f14cbeb071 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts @@ -6,24 +6,30 @@ * Public License, v 1. */ +import type { SavedObject } from '../../../types'; import { SavedObjectsExporter } from './saved_objects_exporter'; import { savedObjectsClientMock } from '../service/saved_objects_client.mock'; +import { SavedObjectTypeRegistry } from '../saved_objects_type_registry'; +import { httpServerMock } from '../../http/http_server.mocks'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '@kbn/utils'; -async function readStreamToCompletion(stream: Readable) { +async function readStreamToCompletion(stream: Readable): Promise>> { return createPromiseFromStreams([stream, createConcatStream([])]); } const exportSizeLimit = 500; +const request = httpServerMock.createKibanaRequest(); describe('getSortedObjectsForExport()', () => { let savedObjectsClient: ReturnType; + let typeRegistry: SavedObjectTypeRegistry; let exporter: SavedObjectsExporter; beforeEach(() => { + typeRegistry = new SavedObjectTypeRegistry(); savedObjectsClient = savedObjectsClientMock.create(); - exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit, typeRegistry }); }); describe('#exportByTypes', () => { @@ -56,6 +62,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], }); @@ -115,6 +122,52 @@ describe('getSortedObjectsForExport()', () => { `); }); + test('applies the export transforms', async () => { + typeRegistry.registerType({ + name: 'foo', + mappings: { properties: {} }, + namespaceType: 'single', + hidden: false, + management: { + importableAndExportable: true, + onExport: (ctx, objects) => { + objects.forEach((obj: SavedObject) => { + obj.attributes.foo = 'modified'; + }); + return objects; + }, + }, + }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit, typeRegistry }); + + savedObjectsClient.find.mockResolvedValueOnce({ + total: 1, + saved_objects: [ + { + id: '1', + type: 'foo', + attributes: { + foo: 'initial', + }, + score: 0, + references: [], + }, + ], + per_page: 1, + page: 0, + }); + const exportStream = await exporter.exportByTypes({ + request, + types: ['foo'], + excludeExportDetails: true, + }); + + const response = await readStreamToCompletion(exportStream); + + expect(response).toHaveLength(1); + expect(response[0].attributes.foo).toEqual('modified'); + }); + test('omits the `namespaces` property from the export', async () => { savedObjectsClient.find.mockResolvedValueOnce({ total: 2, @@ -146,6 +199,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], }); @@ -234,6 +288,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], excludeExportDetails: true, }); @@ -293,6 +348,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], search: 'foo', }); @@ -375,6 +431,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], hasReference: [ { @@ -468,6 +525,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], namespace: 'foo', }); @@ -531,7 +589,7 @@ describe('getSortedObjectsForExport()', () => { }); test('export selected types throws error when exceeding exportSizeLimit', async () => { - exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1 }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1, typeRegistry }); savedObjectsClient.find.mockResolvedValueOnce({ total: 2, @@ -562,6 +620,7 @@ describe('getSortedObjectsForExport()', () => { }); await expect( exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Can't export more than 1 objects"`); @@ -603,6 +662,7 @@ describe('getSortedObjectsForExport()', () => { ], }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern'], }); const response = await readStreamToCompletion(exportStream); @@ -667,6 +727,7 @@ describe('getSortedObjectsForExport()', () => { ], }); const exportStream = await exporter.exportByObjects({ + request, objects: [ { type: 'index-pattern', @@ -759,6 +820,7 @@ describe('getSortedObjectsForExport()', () => { }); await expect( exporter.exportByObjects({ + request, objects: [ { type: 'index-pattern', @@ -774,9 +836,10 @@ describe('getSortedObjectsForExport()', () => { }); test('export selected objects throws error when exceeding exportSizeLimit', async () => { - exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1 }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1, typeRegistry }); const exportOpts = { + request, objects: [ { type: 'index-pattern', @@ -803,6 +866,7 @@ describe('getSortedObjectsForExport()', () => { ], }); const exportStream = await exporter.exportByObjects({ + request, objects: [ { type: 'multi', id: '1' }, { type: 'multi', id: '2' }, @@ -846,6 +910,7 @@ describe('getSortedObjectsForExport()', () => { ], }); const exportStream = await exporter.exportByObjects({ + request, objects: [ { type: 'search', diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.ts b/src/core/server/saved_objects/export/saved_objects_exporter.ts index 7588f13a57644..bd3e60fc1a140 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.ts @@ -9,6 +9,7 @@ import { createListStream } from '@kbn/utils'; import { PublicMethodsOf } from '@kbn/utility-types'; import { SavedObject, SavedObjectsClientContract } from '../types'; +import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry'; import { fetchNestedDependencies } from './fetch_nested_dependencies'; import { sortObjects } from './sort_objects'; import { @@ -16,8 +17,11 @@ import { SavedObjectExportBaseOptions, SavedObjectsExportByObjectOptions, SavedObjectsExportByTypeOptions, + SavedObjectsExportTransform, } from './types'; import { SavedObjectsExportError } from './errors'; +import { applyExportTransforms } from './apply_export_transforms'; +import { byIdAscComparator, getPreservedOrderComparator, SavedObjectComparator } from './utils'; /** * @public @@ -29,17 +33,29 @@ export type ISavedObjectsExporter = PublicMethodsOf; */ export class SavedObjectsExporter { readonly #savedObjectsClient: SavedObjectsClientContract; + readonly #exportTransforms: Record; readonly #exportSizeLimit: number; constructor({ savedObjectsClient, + typeRegistry, exportSizeLimit, }: { savedObjectsClient: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; exportSizeLimit: number; }) { this.#savedObjectsClient = savedObjectsClient; this.#exportSizeLimit = exportSizeLimit; + this.#exportTransforms = typeRegistry.getAllTypes().reduce((transforms, type) => { + if (type.management?.onExport) { + return { + ...transforms, + [type.name]: type.management.onExport, + }; + } + return transforms; + }, {} as Record); } /** @@ -51,7 +67,8 @@ export class SavedObjectsExporter { */ public async exportByTypes(options: SavedObjectsExportByTypeOptions) { const objects = await this.fetchByTypes(options); - return this.processObjects(objects, { + return this.processObjects(objects, byIdAscComparator, { + request: options.request, includeReferencesDeep: options.includeReferencesDeep, excludeExportDetails: options.excludeExportDetails, namespace: options.namespace, @@ -70,7 +87,9 @@ export class SavedObjectsExporter { throw SavedObjectsExportError.exportSizeExceeded(this.#exportSizeLimit); } const objects = await this.fetchByObjects(options); - return this.processObjects(objects, { + const comparator = getPreservedOrderComparator(objects); + return this.processObjects(objects, comparator, { + request: options.request, includeReferencesDeep: options.includeReferencesDeep, excludeExportDetails: options.excludeExportDetails, namespace: options.namespace, @@ -79,7 +98,9 @@ export class SavedObjectsExporter { private async processObjects( savedObjects: SavedObject[], + sortFunction: SavedObjectComparator, { + request, excludeExportDetails = false, includeReferencesDeep = false, namespace, @@ -88,6 +109,13 @@ export class SavedObjectsExporter { let exportedObjects: Array>; let missingReferences: SavedObjectsExportResultDetails['missingReferences'] = []; + savedObjects = await applyExportTransforms({ + request, + objects: savedObjects, + transforms: this.#exportTransforms, + sortFunction, + }); + if (includeReferencesDeep) { const fetchResult = await fetchNestedDependencies( savedObjects, @@ -145,7 +173,7 @@ export class SavedObjectsExporter { findResponse.saved_objects // exclude the find-specific `score` property from the exported objects .map(({ score, ...obj }) => obj) - .sort((a: SavedObject, b: SavedObject) => (a.id > b.id ? 1 : -1)) + .sort(byIdAscComparator) ); } } diff --git a/src/core/server/saved_objects/export/types.ts b/src/core/server/saved_objects/export/types.ts index d8d162e51c294..bf7b265e45d29 100644 --- a/src/core/server/saved_objects/export/types.ts +++ b/src/core/server/saved_objects/export/types.ts @@ -6,10 +6,13 @@ * Public License, v 1. */ -import { SavedObjectsFindOptionsReference } from '../types'; +import { KibanaRequest } from '../../http'; +import { SavedObject, SavedObjectsFindOptionsReference } from '../types'; /** @public */ export interface SavedObjectExportBaseOptions { + /** The http request initiating the export. */ + request: KibanaRequest; /** flag to also include all related saved objects in the export stream. */ includeReferencesDeep?: boolean; /** flag to not append {@link SavedObjectsExportResultDetails | export details} to the end of the export stream. */ @@ -64,3 +67,92 @@ export interface SavedObjectsExportResultDetails { type: string; }>; } + +/** + * Context passed down to a {@link SavedObjectsExportTransform | export transform function} + * + * @public + */ +export interface SavedObjectsExportTransformContext { + /** + * The request that initiated the export request. Can be used to create scoped + * services or client inside the {@link SavedObjectsExportTransform | transformation} + */ + request: KibanaRequest; +} + +/** + * Transformation function used to mutate the exported objects of the associated type. + * + * A type's export transform function will be executed once per user-initiated export, + * for all objects of that type. + * + * @example + * Registering a transform function changing the object's attributes during the export + * ```ts + * // src/plugins/my_plugin/server/plugin.ts + * import { myType } from './saved_objects'; + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * core.savedObjects.registerType({ + * ...myType, + * management: { + * ...myType.management, + * onExport: (ctx, objects) => { + * return objects.map((obj) => ({ + * ...obj, + * attributes: { + * ...obj.attributes, + * enabled: false, + * } + * }) + * } + * }, + * }); + * } + * } + * ``` + * + * @example + * Registering a transform function adding additional objects to the export + * ```ts + * // src/plugins/my_plugin/server/plugin.ts + * import { myType } from './saved_objects'; + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * const savedObjectStartContractPromise = getStartServices().then( + * ([{ savedObjects: savedObjectsStart }]) => savedObjectsStart + * ); + * + * core.savedObjects.registerType({ + * ...myType, + * management: { + * ...myType.management, + * onExport: async (ctx, objects) => { + * const { getScopedClient } = await savedObjectStartContractPromise; + * const client = getScopedClient(ctx.request); + * + * const depResponse = await client.find({ + * type: 'my-nested-object', + * hasReference: objs.map(({ id, type }) => ({ id, type })), + * }); + * + * return [...objs, ...depResponse.saved_objects]; + * } + * }, + * }); + * } + * } + * ``` + * + * @remarks Trying to change an object's id or type during the transform will result in + * a runtime error during the export process. + * + * @public + */ +export type SavedObjectsExportTransform = ( + context: SavedObjectsExportTransformContext, + objects: Array> +) => SavedObject[] | Promise; diff --git a/src/core/server/saved_objects/export/utils.test.ts b/src/core/server/saved_objects/export/utils.test.ts new file mode 100644 index 0000000000000..c547aa2271cf0 --- /dev/null +++ b/src/core/server/saved_objects/export/utils.test.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { byIdAscComparator, getPreservedOrderComparator } from './utils'; +import { SavedObject } from '../../../types'; + +const createObj = (id: string): SavedObject => ({ + id, + type: 'dummy', + attributes: {}, + references: [], +}); + +describe('byIdAscComparator', () => { + it('sorts the objects by id asc', () => { + const objs = [createObj('delta'), createObj('alpha'), createObj('beta')]; + + objs.sort(byIdAscComparator); + + expect(objs.map((obj) => obj.id)).toEqual(['alpha', 'beta', 'delta']); + }); +}); + +describe('getPreservedOrderComparator', () => { + it('sorts objects depending on the order of the provided list', () => { + const objA = createObj('A'); + const objB = createObj('B'); + const objC = createObj('C'); + + const comparator = getPreservedOrderComparator([objA, objB, objC]); + + const objs = [objC, objA, objB]; + objs.sort(comparator); + + expect(objs.map((obj) => obj.id)).toEqual(['A', 'B', 'C']); + }); + + it('appends unknown objects at the end of the list and sort them by id', () => { + const objA = createObj('A'); + const objB = createObj('B'); + const objC = createObj('C'); + const addedA = createObj('addedA'); + const addedB = createObj('addedB'); + + const comparator = getPreservedOrderComparator([objA, objB, objC]); + + const objs = [addedB, objC, addedA, objA, objB]; + objs.sort(comparator); + + expect(objs.map((obj) => obj.id)).toEqual(['A', 'B', 'C', 'addedA', 'addedB']); + }); +}); diff --git a/src/core/server/saved_objects/export/utils.ts b/src/core/server/saved_objects/export/utils.ts new file mode 100644 index 0000000000000..e8567c6da1dca --- /dev/null +++ b/src/core/server/saved_objects/export/utils.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { SavedObject } from '../../../types'; + +export type SavedObjectComparator = (a: SavedObject, b: SavedObject) => number; + +export const getObjKey = (obj: SavedObject) => `${obj.type}|${obj.id}`; + +export const byIdAscComparator: SavedObjectComparator = (a: SavedObject, b: SavedObject) => + a.id > b.id ? 1 : -1; + +/** + * Create a comparator that will sort objects depending on their position in the provided array. + * Objects not present in the array will be appended at the end of the list, and sorted by id asc. + * + * @example + * ```ts + * const comparator = getPreservedOrderComparator([objA, objB, objC]); + * const list = [newB, objB, objC, newA, objA]; // with obj.title matching their variable name + * list.sort() + * // list = [objA, objB, objC, newA, newB] + * ``` + */ +export const getPreservedOrderComparator = (objects: SavedObject[]): SavedObjectComparator => { + const orderedKeys = objects.map(getObjKey); + return (a: SavedObject, b: SavedObject) => { + const indexA = orderedKeys.indexOf(getObjKey(a)); + const indexB = orderedKeys.indexOf(getObjKey(b)); + if (indexA > -1 && indexB > -1) { + return indexA - indexB > 0 ? 1 : -1; + } + if (indexA > -1) { + return -1; + } + if (indexB > -1) { + return 1; + } + return byIdAscComparator(a, b); + }; +}; diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 86ee7de5fab54..9cf400a65030f 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -38,6 +38,8 @@ export { SavedObjectsExportByObjectOptions, SavedObjectsExportResultDetails, SavedObjectsExportError, + SavedObjectsExportTransformContext, + SavedObjectsExportTransform, } from './export'; export { diff --git a/src/core/server/saved_objects/routes/export.ts b/src/core/server/saved_objects/routes/export.ts index aac3f98898e42..9b40855afec2e 100644 --- a/src/core/server/saved_objects/routes/export.ts +++ b/src/core/server/saved_objects/routes/export.ts @@ -10,7 +10,7 @@ import { schema } from '@kbn/config-schema'; import stringify from 'json-stable-stringify'; import { createPromiseFromStreams, createMapStream, createConcatStream } from '@kbn/utils'; -import { IRouter } from '../../http'; +import { IRouter, KibanaRequest } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; import { SavedObjectConfig } from '../saved_objects_config'; import { @@ -78,7 +78,11 @@ const validateOptions = ( includeReferencesDeep, search, }: ExportOptions, - { exportSizeLimit, supportedTypes }: { exportSizeLimit: number; supportedTypes: string[] } + { + exportSizeLimit, + supportedTypes, + request, + }: { exportSizeLimit: number; supportedTypes: string[]; request: KibanaRequest } ): EitherExportOptions => { const hasTypes = (types?.length ?? 0) > 0; const hasObjects = (objects?.length ?? 0) > 0; @@ -106,6 +110,7 @@ const validateOptions = ( objects: objects!, excludeExportDetails, includeReferencesDeep, + request, }; } else { const validationError = validateTypes(types!, supportedTypes); @@ -118,6 +123,7 @@ const validateOptions = ( search, excludeExportDetails, includeReferencesDeep, + request, }; } }; @@ -165,6 +171,7 @@ export const registerExportRoute = ( let options: EitherExportOptions; try { options = validateOptions(cleaned, { + request: req, exportSizeLimit: maxImportExportSize, supportedTypes, }); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 40c8c576b0eca..131873d14b79a 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -457,6 +457,7 @@ export class SavedObjectsService createExporter: (savedObjectsClient) => new SavedObjectsExporter({ savedObjectsClient, + typeRegistry: this.typeRegistry, exportSizeLimit: this.config!.maxImportExportSize, }), createImporter: (savedObjectsClient) => diff --git a/src/core/server/saved_objects/saved_objects_type_registry.test.ts b/src/core/server/saved_objects/saved_objects_type_registry.test.ts index 7c91baa73c676..0186af6e7628d 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.test.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.test.ts @@ -25,25 +25,68 @@ describe('SavedObjectTypeRegistry', () => { registry = new SavedObjectTypeRegistry(); }); - it('allows to register types', () => { - registry.registerType(createType({ name: 'typeA' })); - registry.registerType(createType({ name: 'typeB' })); - registry.registerType(createType({ name: 'typeC' })); - - expect( - registry - .getAllTypes() - .map((type) => type.name) - .sort() - ).toEqual(['typeA', 'typeB', 'typeC']); - }); + describe('#registerType', () => { + it('allows to register types', () => { + registry.registerType(createType({ name: 'typeA' })); + registry.registerType(createType({ name: 'typeB' })); + registry.registerType(createType({ name: 'typeC' })); + + expect( + registry + .getAllTypes() + .map((type) => type.name) + .sort() + ).toEqual(['typeA', 'typeB', 'typeC']); + }); - it('throws when trying to register the same type twice', () => { - registry.registerType(createType({ name: 'typeA' })); - registry.registerType(createType({ name: 'typeB' })); - expect(() => { + it('throws when trying to register the same type twice', () => { registry.registerType(createType({ name: 'typeA' })); - }).toThrowErrorMatchingInlineSnapshot(`"Type 'typeA' is already registered"`); + registry.registerType(createType({ name: 'typeB' })); + expect(() => { + registry.registerType(createType({ name: 'typeA' })); + }).toThrowErrorMatchingInlineSnapshot(`"Type 'typeA' is already registered"`); + }); + + it('throws when `management.onExport` is specified but `management.importableAndExportable` is undefined or false', () => { + expect(() => { + registry.registerType( + createType({ + name: 'typeA', + management: { + onExport: (ctx, objs) => objs, + }, + }) + ); + }).toThrowErrorMatchingInlineSnapshot( + `"Type typeA: 'management.importableAndExportable' must be 'true' when specifying 'management.onExport'"` + ); + expect(() => { + registry.registerType( + createType({ + name: 'typeA', + management: { + importableAndExportable: false, + onExport: (ctx, objs) => objs, + }, + }) + ); + }).toThrowErrorMatchingInlineSnapshot( + `"Type typeA: 'management.importableAndExportable' must be 'true' when specifying 'management.onExport'"` + ); + expect(() => { + registry.registerType( + createType({ + name: 'typeA', + management: { + importableAndExportable: true, + onExport: (ctx, objs) => objs, + }, + }) + ); + }).not.toThrow(); + }); + + // TODO: same test with 'onImport' }); describe('#getType', () => { diff --git a/src/core/server/saved_objects/saved_objects_type_registry.ts b/src/core/server/saved_objects/saved_objects_type_registry.ts index 2194353a07583..d2cee700bf66d 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.ts @@ -32,6 +32,7 @@ export class SavedObjectTypeRegistry { if (this.types.has(type.name)) { throw new Error(`Type '${type.name}' is already registered`); } + validateType(type); this.types.set(type.name, deepFreeze(type)); } @@ -116,3 +117,13 @@ export class SavedObjectTypeRegistry { return this.types.get(type)?.management?.importableAndExportable ?? false; } } + +const validateType = ({ name, management }: SavedObjectsType) => { + if (management) { + if (management.onExport && !management.importableAndExportable) { + throw new Error( + `Type ${name}: 'management.importableAndExportable' must be 'true' when specifying 'management.onExport'` + ); + } + } +}; diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 7fab03aab4d0f..4f47579741a5a 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -9,6 +9,7 @@ import { SavedObjectsClient } from './service/saved_objects_client'; import { SavedObjectsTypeMappingDefinition } from './mappings'; import { SavedObjectMigrationMap } from './migrations'; +import { SavedObjectsExportTransform } from './export'; import { SavedObjectsImportHook } from './import/types'; export { @@ -320,6 +321,17 @@ export interface SavedObjectsTypeManagementDefinition { * {@link Capabilities | uiCapabilities} to check if the user has permission to access the object. */ getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string }; + /** + * An optional export transform function that can be used transform the objects of the registered type during + * the export process. + * + * It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list. + * + * See {@link SavedObjectsExportTransform | the transform type documentation} for more info and examples. + * + * @remarks `importableAndExportable` must be `true` to specify this property. + */ + onExport?: SavedObjectsExportTransform; /** * An optional {@link SavedObjectsImportHook | import hook} to use when importing given type. * @@ -359,7 +371,8 @@ export interface SavedObjectsTypeManagementDefinition { * } * ``` * - * @remark messages returned in the warnings are user facing and must be translated. + * @remarks messages returned in the warnings are user facing and must be translated. + * @remarks `importableAndExportable` must be `true` to specify this property. */ onImport?: SavedObjectsImportHook; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 50d4a5bf502d6..ceab69a6cdb18 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2078,6 +2078,7 @@ export interface SavedObjectExportBaseOptions { excludeExportDetails?: boolean; includeReferencesDeep?: boolean; namespace?: string; + request: KibanaRequest; } // @public @@ -2402,8 +2403,9 @@ export interface SavedObjectsExportByTypeOptions extends SavedObjectExportBaseOp export class SavedObjectsExporter { // (undocumented) #private; - constructor({ savedObjectsClient, exportSizeLimit, }: { + constructor({ savedObjectsClient, typeRegistry, exportSizeLimit, }: { savedObjectsClient: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; exportSizeLimit: number; }); exportByObjects(options: SavedObjectsExportByObjectOptions): Promise; @@ -2417,8 +2419,10 @@ export class SavedObjectsExportError extends Error { readonly attributes?: Record | undefined; // (undocumented) static exportSizeExceeded(limit: number): SavedObjectsExportError; + static invalidTransformError(objectKeys: string[]): SavedObjectsExportError; // (undocumented) static objectFetchError(objects: SavedObject[]): SavedObjectsExportError; + static objectTransformError(objects: SavedObject[], cause: Error): SavedObjectsExportError; // (undocumented) readonly type: string; } @@ -2433,6 +2437,14 @@ export interface SavedObjectsExportResultDetails { }>; } +// @public +export type SavedObjectsExportTransform = (context: SavedObjectsExportTransformContext, objects: Array>) => SavedObject[] | Promise; + +// @public +export interface SavedObjectsExportTransformContext { + request: KibanaRequest; +} + // @public export type SavedObjectsFieldMapping = SavedObjectsCoreFieldMapping | SavedObjectsComplexFieldMapping; @@ -2851,6 +2863,7 @@ export interface SavedObjectsTypeManagementDefinition { getTitle?: (savedObject: SavedObject) => string; icon?: string; importableAndExportable?: boolean; + onExport?: SavedObjectsExportTransform; onImport?: SavedObjectsImportHook; } diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 900b4bd3f4b9f..74f9fb65db54d 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -8,7 +8,34 @@ /** This module is intended for consumption by public to avoid import issues with server-side code */ export { PluginOpaqueId } from './plugins/types'; -export * from './saved_objects/types'; +export type { + SavedObjectsImportResponse, + SavedObjectsImportSuccess, + SavedObjectsImportConflictError, + SavedObjectsImportAmbiguousConflictError, + SavedObjectsImportUnsupportedTypeError, + SavedObjectsImportMissingReferencesError, + SavedObjectsImportUnknownError, + SavedObjectsImportFailure, + SavedObjectsImportRetry, + SavedObjectsImportWarning, + SavedObjectsImportActionRequiredWarning, + SavedObjectsImportSimpleWarning, + SavedObjectAttributes, + SavedObjectAttribute, + SavedObjectAttributeSingle, + SavedObject, + SavedObjectError, + SavedObjectReference, + SavedObjectsMigrationVersion, + SavedObjectStatusMeta, + SavedObjectsFindOptionsReference, + SavedObjectsFindOptions, + SavedObjectsBaseOptions, + MutatingOperationRefreshSetting, + SavedObjectsClientContract, + SavedObjectsNamespaceType, +} from './saved_objects/types'; export * from './ui_settings/types'; export * from './legacy/types'; export type { EnvironmentMode, PackageInfo } from '@kbn/config'; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 048d60dbc25c6..d6bd896a584a4 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -24,6 +24,7 @@ import * as CSS from 'csstype'; import { Datatable as Datatable_2 } from 'src/plugins/expressions'; import { Datatable as Datatable_3 } from 'src/plugins/expressions/common'; import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions'; +import { DetailedPeerCertificate } from 'tls'; import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; import { ErrorToastOptions } from 'src/core/public/notifications'; @@ -45,6 +46,7 @@ import { History } from 'history'; import { Href } from 'history'; import { HttpSetup } from 'kibana/public'; import { IconType } from '@elastic/eui'; +import { IncomingHttpHeaders } from 'http'; import { InjectedIntl } from '@kbn/i18n/react'; import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource as ISearchSource_2 } from 'src/plugins/data/public'; @@ -60,9 +62,11 @@ import { METRIC_TYPE } from '@kbn/analytics'; import { Moment } from 'moment'; import moment from 'moment'; import { NameList } from 'elasticsearch'; +import { ObjectType } from '@kbn/config-schema'; import { Observable } from 'rxjs'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; +import { PeerCertificate } from 'tls'; import { Plugin as Plugin_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public'; @@ -75,6 +79,7 @@ import React from 'react'; import * as React_3 from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; import { Reporter } from '@kbn/analytics'; +import { Request as Request_2 } from '@hapi/hapi'; import { RequestAdapter } from 'src/plugins/inspector/common'; import { RequestStatistics as RequestStatistics_2 } from 'src/plugins/inspector/common'; import { Required } from '@kbn/utility-types'; @@ -85,6 +90,7 @@ import { SavedObjectReference } from 'src/core/types'; import { SavedObjectsClientContract } from 'src/core/public'; import { SavedObjectsFindOptions } from 'kibana/public'; import { SavedObjectsFindResponse } from 'kibana/server'; +import { SchemaTypeError } from '@kbn/config-schema'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; @@ -94,12 +100,14 @@ import { ToastsSetup } from 'kibana/public'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; +import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { UiCounterMetricType } from '@kbn/analytics'; import { Unit } from '@elastic/datemath'; import { UnregisterCallback } from 'history'; +import { URL } from 'url'; import { UserProvidedValues } from 'src/core/server/types'; // Warning: (ae-missing-release-tag) "ACTION_GLOBAL_APPLY_FILTER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index f41d92dfb65a4..2f9b43121b45a 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -12,6 +12,7 @@ import { ApplicationStart as ApplicationStart_2 } from 'kibana/public'; import Boom from '@hapi/boom'; import { ConfigDeprecationProvider } from '@kbn/config'; import * as CSS from 'csstype'; +import { DetailedPeerCertificate } from 'tls'; import { EmbeddableStart as EmbeddableStart_2 } from 'src/plugins/embeddable/public/plugin'; import { EnvironmentMode } from '@kbn/config'; import { EuiBreadcrumb } from '@elastic/eui'; @@ -25,6 +26,7 @@ import { History } from 'history'; import { Href } from 'history'; import { I18nStart as I18nStart_2 } from 'src/core/public'; import { IconType } from '@elastic/eui'; +import { IncomingHttpHeaders } from 'http'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; @@ -32,30 +34,36 @@ import { Logger } from '@kbn/logging'; import { LogMeta } from '@kbn/logging'; import { MaybePromise } from '@kbn/utility-types'; import { NotificationsStart as NotificationsStart_2 } from 'src/core/public'; +import { ObjectType } from '@kbn/config-schema'; import { Observable } from 'rxjs'; import { Optional } from '@kbn/utility-types'; import { OverlayRef as OverlayRef_2 } from 'src/core/public'; import { OverlayStart as OverlayStart_2 } from 'src/core/public'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; +import { PeerCertificate } from 'tls'; import { PluginInitializerContext } from 'src/core/public'; import * as PropTypes from 'prop-types'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams } from 'src/core/server/types'; import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; +import { Request } from '@hapi/hapi'; import * as Rx from 'rxjs'; import { SavedObjectAttributes } from 'kibana/server'; import { SavedObjectAttributes as SavedObjectAttributes_2 } from 'src/core/public'; import { SavedObjectAttributes as SavedObjectAttributes_3 } from 'kibana/public'; +import { SchemaTypeError } from '@kbn/config-schema'; import { SimpleSavedObject as SimpleSavedObject_2 } from 'src/core/public'; import { Start as Start_2 } from 'src/plugins/inspector/public'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; +import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UiComponent } from 'src/plugins/kibana_utils/public'; import { UnregisterCallback } from 'history'; +import { URL } from 'url'; import { UserProvidedValues } from 'src/core/server/types'; // Warning: (ae-missing-release-tag) "ACTION_ADD_PANEL" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json new file mode 100644 index 0000000000000..7f4043958bc89 --- /dev/null +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json @@ -0,0 +1,149 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-transform:type_1-obj_1", + "source": { + "test-export-transform": { + "title": "test_1-obj_1", + "enabled": true + }, + "type": "test-export-transform", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-transform:type_1-obj_2", + "source": { + "test-export-transform": { + "title": "test_1-obj_2", + "enabled": true + }, + "type": "test-export-transform", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-add:type_2-obj_1", + "source": { + "test-export-add": { + "title": "test_2-obj_1" + }, + "type": "test-export-add", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-add:type_2-obj_2", + "source": { + "test-export-add": { + "title": "test_2-obj_2" + }, + "type": "test-export-add", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-add-dep:type_dep-obj_1", + "source": { + "test-export-add-dep": { + "title": "type_dep-obj_1" + }, + "type": "test-export-add-dep", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z", + "references": [ + { + "type": "test-export-add", + "id": "type_2-obj_1" + } + ] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-add-dep:type_dep-obj_2", + "source": { + "test-export-add-dep": { + "title": "type_dep-obj_2" + }, + "type": "test-export-add-dep", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z", + "references": [ + { + "type": "test-export-add", + "id": "type_2-obj_2" + } + ] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-invalid-transform:type_3-obj_1", + "source": { + "test-export-invalid-transform": { + "title": "test_2-obj_1" + }, + "type": "test-export-invalid-transform", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-transform-error:type_4-obj_1", + "source": { + "test-export-transform-error": { + "title": "test_2-obj_1" + }, + "type": "test-export-transform-error", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json new file mode 100644 index 0000000000000..d85125efd672a --- /dev/null +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json @@ -0,0 +1,499 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "auto_expand_replicas": "0-1", + "number_of_replicas": "0" + } + }, + "mappings": { + "dynamic": "strict", + "properties": { + "test-export-transform": { + "properties": { + "title": { "type": "text" }, + "enabled": { "type": "boolean" } + } + }, + "test-export-add": { + "properties": { + "title": { "type": "text" } + } + }, + "test-export-add-dep": { + "properties": { + "title": { "type": "text" } + } + }, + "test-export-transform-error": { + "properties": { + "title": { "type": "text" } + } + }, + "test-export-invalid-transform": { + "properties": { + "title": { "type": "text" } + } + }, + "apm-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "go": { + "type": "long", + "null_value": 0 + }, + "java": { + "type": "long", + "null_value": 0 + }, + "js-base": { + "type": "long", + "null_value": 0 + }, + "nodejs": { + "type": "long", + "null_value": 0 + }, + "python": { + "type": "long", + "null_value": 0 + }, + "ruby": { + "type": "long", + "null_value": 0 + } + } + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "id": { + "type": "text", + "index": false + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "accessibility:disableAnimations": { + "type": "boolean" + }, + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "telemetry:optIn": { + "type": "boolean" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape", + "tree": "quadtree" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "space": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "initials": { + "type": "keyword" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "spaceId": { + "type": "keyword" + }, + "telemetry": { + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } +} diff --git a/test/plugin_functional/plugins/saved_object_export_transforms/kibana.json b/test/plugin_functional/plugins/saved_object_export_transforms/kibana.json new file mode 100644 index 0000000000000..40b4c12f58e69 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_transforms/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "savedObjectExportTransforms", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["saved_object_export_transforms"], + "server": true, + "ui": false +} diff --git a/test/plugin_functional/plugins/saved_object_export_transforms/package.json b/test/plugin_functional/plugins/saved_object_export_transforms/package.json new file mode 100644 index 0000000000000..0ced0a3b21288 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_transforms/package.json @@ -0,0 +1,14 @@ +{ + "name": "saved_object_export_transforms", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/saved_object_export_transforms", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && ../../../../node_modules/.bin/tsc" + } +} \ No newline at end of file diff --git a/test/plugin_functional/plugins/saved_object_export_transforms/server/index.ts b/test/plugin_functional/plugins/saved_object_export_transforms/server/index.ts new file mode 100644 index 0000000000000..f87a7d7d2e6a3 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_transforms/server/index.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { SavedObjectExportTransformsPlugin } from './plugin'; + +export const plugin = () => new SavedObjectExportTransformsPlugin(); diff --git a/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts b/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts new file mode 100644 index 0000000000000..acbf454a93093 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { Plugin, CoreSetup } from 'kibana/server'; + +export class SavedObjectExportTransformsPlugin implements Plugin { + public setup({ savedObjects, getStartServices }: CoreSetup, deps: {}) { + const savedObjectStartContractPromise = getStartServices().then( + ([{ savedObjects: savedObjectsStart }]) => savedObjectsStart + ); + + // example of a SO type that will mutates its properties + // during the export transform + savedObjects.registerType({ + name: 'test-export-transform', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + enabled: { + type: 'boolean', + }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + onExport: (ctx, objs) => { + return objs.map((obj) => ({ + ...obj, + attributes: { + ...obj.attributes, + enabled: false, + }, + })); + }, + }, + }); + + // example of a SO type that will add additional objects + // to the export during the export transform + savedObjects.registerType({ + name: 'test-export-add', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + onExport: async (ctx, objs) => { + const { getScopedClient } = await savedObjectStartContractPromise; + const client = getScopedClient(ctx.request); + const objRefs = objs.map(({ id, type }) => ({ id, type })); + const depResponse = await client.find({ + type: 'test-export-add-dep', + hasReference: objRefs, + }); + return [...objs, ...depResponse.saved_objects]; + }, + }, + }); + + // dependency of `test_export_transform_2` that will be included + // when exporting them + savedObjects.registerType({ + name: 'test-export-add-dep', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + }, + }); + + ///////////// + ///////////// + // example of a SO type that will throw an object-transform-error + savedObjects.registerType({ + name: 'test-export-transform-error', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + onExport: (ctx, objs) => { + throw new Error('Error during transform'); + }, + }, + }); + + // example of a SO type that will throw an invalid-transform-error + savedObjects.registerType({ + name: 'test-export-invalid-transform', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + onExport: (ctx, objs) => { + return objs.map((obj) => ({ + ...obj, + id: `${obj.id}-mutated`, + })); + }, + }, + }); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/saved_object_export_transforms/tsconfig.json b/test/plugin_functional/plugins/saved_object_export_transforms/tsconfig.json new file mode 100644 index 0000000000000..da457c9ba32fc --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_transforms/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [], + "references": [ + { "path": "../../../../src/core/tsconfig.json" } + ] +} diff --git a/test/plugin_functional/plugins/saved_object_hooks/kibana.json b/test/plugin_functional/plugins/saved_object_import_warnings/kibana.json similarity index 50% rename from test/plugin_functional/plugins/saved_object_hooks/kibana.json rename to test/plugin_functional/plugins/saved_object_import_warnings/kibana.json index 1580e1862fac1..947f840560eba 100644 --- a/test/plugin_functional/plugins/saved_object_hooks/kibana.json +++ b/test/plugin_functional/plugins/saved_object_import_warnings/kibana.json @@ -1,8 +1,8 @@ { - "id": "savedObjectHooks", + "id": "savedObjectImportWarnings", "version": "0.0.1", "kibanaVersion": "kibana", - "configPath": ["saved_object_hooks"], + "configPath": ["saved_object_import_warnings"], "server": true, "ui": false } diff --git a/test/plugin_functional/plugins/saved_object_hooks/package.json b/test/plugin_functional/plugins/saved_object_import_warnings/package.json similarity index 68% rename from test/plugin_functional/plugins/saved_object_hooks/package.json rename to test/plugin_functional/plugins/saved_object_import_warnings/package.json index 9e09e5fc94be4..0c3cb50bd0b18 100644 --- a/test/plugin_functional/plugins/saved_object_hooks/package.json +++ b/test/plugin_functional/plugins/saved_object_import_warnings/package.json @@ -1,7 +1,7 @@ { - "name": "saved_object_hooks", + "name": "saved_object_import_warnings", "version": "1.0.0", - "main": "target/test/plugin_functional/plugins/saved_object_hooks", + "main": "target/test/plugin_functional/plugins/saved_object_import_warnings", "kibana": { "version": "kibana", "templateVersion": "1.0.0" diff --git a/test/plugin_functional/plugins/saved_object_hooks/server/index.ts b/test/plugin_functional/plugins/saved_object_import_warnings/server/index.ts similarity index 73% rename from test/plugin_functional/plugins/saved_object_hooks/server/index.ts rename to test/plugin_functional/plugins/saved_object_import_warnings/server/index.ts index 28aaa75961ddc..9a7209480cc19 100644 --- a/test/plugin_functional/plugins/saved_object_hooks/server/index.ts +++ b/test/plugin_functional/plugins/saved_object_import_warnings/server/index.ts @@ -6,6 +6,6 @@ * Public License, v 1. */ -import { SavedObjectHooksPlugin } from './plugin'; +import { SavedObjectImportWarningsPlugin } from './plugin'; -export const plugin = () => new SavedObjectHooksPlugin(); +export const plugin = () => new SavedObjectImportWarningsPlugin(); diff --git a/test/plugin_functional/plugins/saved_object_hooks/server/plugin.ts b/test/plugin_functional/plugins/saved_object_import_warnings/server/plugin.ts similarity index 96% rename from test/plugin_functional/plugins/saved_object_hooks/server/plugin.ts rename to test/plugin_functional/plugins/saved_object_import_warnings/server/plugin.ts index 823d9a90f29e2..5fc4e4aed9b90 100644 --- a/test/plugin_functional/plugins/saved_object_hooks/server/plugin.ts +++ b/test/plugin_functional/plugins/saved_object_import_warnings/server/plugin.ts @@ -8,7 +8,7 @@ import { Plugin, CoreSetup } from 'kibana/server'; -export class SavedObjectHooksPlugin implements Plugin { +export class SavedObjectImportWarningsPlugin implements Plugin { public setup({ savedObjects }: CoreSetup, deps: {}) { savedObjects.registerType({ name: 'test_import_warning_1', diff --git a/test/plugin_functional/plugins/saved_object_hooks/tsconfig.json b/test/plugin_functional/plugins/saved_object_import_warnings/tsconfig.json similarity index 100% rename from test/plugin_functional/plugins/saved_object_hooks/tsconfig.json rename to test/plugin_functional/plugins/saved_object_import_warnings/tsconfig.json diff --git a/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts b/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts new file mode 100644 index 0000000000000..33c4ddc38be07 --- /dev/null +++ b/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import expect from '@kbn/expect'; +import type { SavedObject } from '../../../../src/core/types'; +import { PluginFunctionalProviderContext } from '../../services'; + +function parseNdJson(input: string): Array> { + return input.split('\n').map((str) => JSON.parse(str)); +} + +export default function ({ getService }: PluginFunctionalProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('export transforms', () => { + before(async () => { + await esArchiver.load( + '../functional/fixtures/es_archiver/saved_objects_management/export_transform' + ); + }); + + after(async () => { + await esArchiver.unload( + '../functional/fixtures/es_archiver/saved_objects_management/export_transform' + ); + }); + + it('allows to mutate the objects during an export', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + type: ['test-export-transform'], + excludeExportDetails: true, + }) + .expect(200) + .then((resp) => { + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([ + { + id: 'type_1-obj_1', + enabled: false, + }, + { + id: 'type_1-obj_2', + enabled: false, + }, + ]); + }); + }); + + it('allows to add additional objects to an export', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + objects: [ + { + type: 'test-export-add', + id: 'type_2-obj_1', + }, + ], + excludeExportDetails: true, + }) + .expect(200) + .then((resp) => { + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']); + }); + }); + + it('allows to add additional objects to an export when exporting by type', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + type: ['test-export-add'], + excludeExportDetails: true, + }) + .expect(200) + .then((resp) => { + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => obj.id)).to.eql([ + 'type_2-obj_1', + 'type_2-obj_2', + 'type_dep-obj_1', + 'type_dep-obj_2', + ]); + }); + }); + + it('returns a 400 when the type causes a transform error', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + type: ['test-export-transform-error'], + excludeExportDetails: true, + }) + .expect(400) + .then((resp) => { + const { attributes, ...error } = resp.body; + expect(error).to.eql({ + error: 'Bad Request', + message: 'Error transforming objects to export', + statusCode: 400, + }); + expect(attributes.cause).to.eql('Error during transform'); + expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']); + }); + }); + + it('returns a 400 when the type causes an invalid transform', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + type: ['test-export-invalid-transform'], + excludeExportDetails: true, + }) + .expect(400) + .then((resp) => { + expect(resp.body).to.eql({ + error: 'Bad Request', + message: 'Invalid transform performed on objects to export', + statusCode: 400, + attributes: { + objectKeys: ['test-export-invalid-transform|type_3-obj_1'], + }, + }); + }); + }); + }); +} diff --git a/test/plugin_functional/test_suites/saved_objects_management/index.ts b/test/plugin_functional/test_suites/saved_objects_management/index.ts index ad89a6605bbc5..38c966901d8a2 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/index.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/index.ts @@ -10,6 +10,7 @@ import { PluginFunctionalProviderContext } from '../../services'; export default function ({ loadTestFile }: PluginFunctionalProviderContext) { describe('Saved Objects Management', function () { + loadTestFile(require.resolve('./export_transform')); loadTestFile(require.resolve('./import_warnings')); }); } diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts index 8450fdf6b4641..e06ba3499d9a4 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts @@ -167,6 +167,7 @@ describe('copySavedObjectsToSpaces', () => { `); expect(savedObjectsExporter.exportByObjects).toHaveBeenCalledWith({ + request: expect.any(Object), excludeExportDetails: true, includeReferencesDeep: true, namespace, diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts index 852f680b0245a..39f31c5f85178 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts @@ -29,6 +29,7 @@ export function copySavedObjectsToSpacesFactory( options: Pick ) => { const objectStream = await savedObjectsExporter.exportByObjects({ + request, namespace: spaceIdToNamespace(sourceSpaceId), includeReferencesDeep: options.includeReferences, excludeExportDetails: true, diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts index 0f5de232177fd..6c24394b540df 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts @@ -174,6 +174,7 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => { `); expect(savedObjectsExporter.exportByObjects).toHaveBeenCalledWith({ + request: expect.any(Object), excludeExportDetails: true, includeReferencesDeep: true, namespace, diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts index 2a671b1423e8c..6033e369d3ea4 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts @@ -29,6 +29,7 @@ export function resolveCopySavedObjectsToSpacesConflictsFactory( options: Pick ) => { const objectStream = await savedObjectsExporter.exportByObjects({ + request, namespace: spaceIdToNamespace(sourceSpaceId), includeReferencesDeep: options.includeReferences, excludeExportDetails: true, From 07002d691bba423c9ef7a53a11c3ffef2552aaba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 21 Jan 2021 14:49:18 +0000 Subject: [PATCH 54/83] [Functional tests] Drop `legacyEs` usage (#88939) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/api_integration/apis/saved_objects/resolve.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/api_integration/apis/saved_objects/resolve.ts b/test/api_integration/apis/saved_objects/resolve.ts index b71d5e3003495..fb8baef465f38 100644 --- a/test/api_integration/apis/saved_objects/resolve.ts +++ b/test/api_integration/apis/saved_objects/resolve.ts @@ -12,7 +12,7 @@ import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); + const es = getService('es'); const esArchiver = getService('esArchiver'); describe('resolve', () => { @@ -81,10 +81,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) + await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) ); it('should return basic 404 without mentioning index', async () => From 208ae0d2a48005cfac1d051fff53a33258449045 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 21 Jan 2021 08:34:03 -0700 Subject: [PATCH 55/83] [functional_cors] use pre-defined unique port in CI (#88919) Co-authored-by: spalger --- .ci/teamcity/setup_env.sh | 1 + vars/kibanaPipeline.groovy | 2 ++ x-pack/test/functional_cors/config.ts | 6 ++++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.ci/teamcity/setup_env.sh b/.ci/teamcity/setup_env.sh index 09fcd0357fb7b..8f607323102bc 100755 --- a/.ci/teamcity/setup_env.sh +++ b/.ci/teamcity/setup_env.sh @@ -48,6 +48,7 @@ else fi tc_set_env FLEET_PACKAGE_REGISTRY_PORT 6104 # Any unused port is fine, used by ingest manager tests +tc_set_env TEST_CORS_SERVER_PORT 6105 # Any unused port is fine, used by ingest manager tests if [[ "$(which google-chrome-stable)" || "$(which google-chrome)" ]]; then echo "Chrome detected, setting DETECT_CHROMEDRIVER_VERSION=true" diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 7991dd3252153..93cb7a719bbe8 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -89,6 +89,7 @@ def withFunctionalTestEnv(List additionalEnvs = [], Closure closure) { def esTransportPort = "61${parallelId}3" def fleetPackageRegistryPort = "61${parallelId}4" def alertingProxyPort = "61${parallelId}5" + def corsTestServerPort = "61${parallelId}6" def apmActive = githubPr.isPr() ? "false" : "true" withEnv([ @@ -100,6 +101,7 @@ def withFunctionalTestEnv(List additionalEnvs = [], Closure closure) { "TEST_KIBANA_URL=http://elastic:changeme@localhost:${kibanaPort}", "TEST_ES_URL=http://elastic:changeme@localhost:${esPort}", "TEST_ES_TRANSPORT_PORT=${esTransportPort}", + "TEST_CORS_SERVER_PORT=${corsTestServerPort}", "KBN_NP_PLUGINS_BUILT=true", "FLEET_PACKAGE_REGISTRY_PORT=${fleetPackageRegistryPort}", "ALERTING_PROXY_PORT=${alertingProxyPort}", diff --git a/x-pack/test/functional_cors/config.ts b/x-pack/test/functional_cors/config.ts index 737cccc5ae33b..a5bece6c292e4 100644 --- a/x-pack/test/functional_cors/config.ts +++ b/x-pack/test/functional_cors/config.ts @@ -6,11 +6,14 @@ import Url from 'url'; import Path from 'path'; -import getPort from 'get-port'; import type { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { kbnTestConfig } from '@kbn/test'; import { pageObjects } from '../functional/page_objects'; +const pluginPort = process.env.TEST_CORS_SERVER_PORT + ? parseInt(process.env.TEST_CORS_SERVER_PORT, 10) + : 5699; + export default async function ({ readConfigFile }: FtrConfigProviderContext) { const kibanaFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); @@ -27,7 +30,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { }; const { protocol, hostname } = kbnTestConfig.getUrlParts(); - const pluginPort = await getPort(); const originUrl = Url.format({ protocol, hostname, From 92f0b7cade7446da8c66184a0939f383f7fb372f Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Thu, 21 Jan 2021 15:55:31 +0000 Subject: [PATCH 56/83] [Security Solution] integration test failure on kpi hosts and kpi network (#88870) * integration test failure * integration test failure * integration test failure * kpi_network integration test failure Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apis/security_solution/kpi_hosts.ts | 32 +++++++++++++------ .../apis/security_solution/kpi_network.ts | 6 ++-- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/x-pack/test/api_integration/apis/security_solution/kpi_hosts.ts b/x-pack/test/api_integration/apis/security_solution/kpi_hosts.ts index 7ec8945408303..2731a3565d27f 100644 --- a/x-pack/test/api_integration/apis/security_solution/kpi_hosts.ts +++ b/x-pack/test/api_integration/apis/security_solution/kpi_hosts.ts @@ -122,7 +122,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(body.authenticationsSuccess!).to.eql(expectedResult.authSuccess); expect(body.authenticationsSuccessHistogram!).to.eql(expectedResult.authSuccessHistogram); - expect(body.authenticationsFailure!).to.eql(expectedResult.authSuccess); + expect(body.authenticationsFailure!).to.eql(expectedResult.authFailure); expect(body.authenticationsFailureHistogram!).to.eql(expectedResult.authFailureHistogram); }); @@ -185,14 +185,28 @@ export default function ({ getService }: FtrProviderContext) { y: 6, }, ], - authSuccess: null, + authSuccess: 0, authSuccessHistogram: null, authFailure: 0, authFailureHistogram: null, - uniqueSourceIps: null, - uniqueSourceIpsHistogram: null, - uniqueDestinationIps: null, - uniqueDestinationIpsHistogram: null, + uniqueSourceIps: 370, + uniqueSourceIpsHistogram: [ + { x: 1543276800000, y: 74 }, + { x: 1543278600000, y: 52 }, + { x: 1543280400000, y: 71 }, + { x: 1543282200000, y: 76 }, + { x: 1543284000000, y: 71 }, + { x: 1543285800000, y: 89 }, + ], + uniqueDestinationIps: 1, + uniqueDestinationIpsHistogram: [ + { x: 1543276800000, y: 0 }, + { x: 1543278600000, y: 0 }, + { x: 1543280400000, y: 0 }, + { x: 1543282200000, y: 0 }, + { x: 1543284000000, y: 0 }, + { x: 1543285800000, y: 1 }, + ], }; it('Make sure that we get KpiHosts data', async () => { @@ -227,14 +241,14 @@ export default function ({ getService }: FtrProviderContext) { to: TO, from: FROM, }, - defaultIndex: ['filebeat-*'], + defaultIndex: ['auditbeat-*'], docValueFields: [], inspect: false, }) .expect(200); expect(body.authenticationsSuccess!).to.eql(expectedResult.authSuccess); expect(body.authenticationsSuccessHistogram!).to.eql(expectedResult.authSuccessHistogram); - expect(body.authenticationsFailure!).to.eql(expectedResult.authSuccess); + expect(body.authenticationsFailure!).to.eql(expectedResult.authFailure); expect(body.authenticationsFailureHistogram!).to.eql(expectedResult.authFailureHistogram); }); @@ -249,7 +263,7 @@ export default function ({ getService }: FtrProviderContext) { to: TO, from: FROM, }, - defaultIndex: ['filebeat-*'], + defaultIndex: ['auditbeat-*'], docValueFields: [], inspect: false, }) diff --git a/x-pack/test/api_integration/apis/security_solution/kpi_network.ts b/x-pack/test/api_integration/apis/security_solution/kpi_network.ts index b1802a012179d..5db52ec4d1f33 100644 --- a/x-pack/test/api_integration/apis/security_solution/kpi_network.ts +++ b/x-pack/test/api_integration/apis/security_solution/kpi_network.ts @@ -203,9 +203,9 @@ export default function ({ getService }: FtrProviderContext) { const expectedResult = { networkEvents: 665, uniqueFlowId: 124, - uniqueSourcePrivateIps: null, + uniqueSourcePrivateIps: 0, uniqueSourcePrivateIpsHistogram: null, - uniqueDestinationPrivateIps: null, + uniqueDestinationPrivateIps: 0, uniqueDestinationPrivateIpsHistogram: null, dnsQueries: 0, tlsHandshakes: 1, @@ -302,7 +302,7 @@ export default function ({ getService }: FtrProviderContext) { to: TO, from: FROM, }, - defaultIndex: ['filebeat-*'], + defaultIndex: ['packetbeat-*'], docValueFields: [], inspect: false, }) From 4f62bf1f885f043d2337caa7d45468151207866b Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Thu, 21 Jan 2021 09:19:42 -0700 Subject: [PATCH 57/83] Update geo alerts index description: `geo shape/point` -> `geo point` (#88860) --- .../geo_containment_alert_type_expression.test.tsx.snap | 4 ++-- .../query_builder/expressions/entity_index_expression.tsx | 4 +++- .../geo_threshold_alert_type_expression.test.tsx.snap | 4 ++-- .../query_builder/expressions/entity_index_expression.tsx | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap index 535c883aed536..860686b5211d8 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/__snapshots__/geo_containment_alert_type_expression.test.tsx.snap @@ -77,7 +77,7 @@ exports[`should render BoundaryIndexExpression 1`] = ` exports[`should render EntityIndexExpression 1`] = ` = ({ = ({ Date: Thu, 21 Jan 2021 10:32:27 -0600 Subject: [PATCH 58/83] [build/fs] Fix copyAll default atime and mtime (#88921) --- src/dev/build/lib/fs.ts | 4 ++-- src/dev/build/lib/integration_tests/fs.test.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/dev/build/lib/fs.ts b/src/dev/build/lib/fs.ts index 451462d0cb14e..f01fbc6283ec5 100644 --- a/src/dev/build/lib/fs.ts +++ b/src/dev/build/lib/fs.ts @@ -153,7 +153,7 @@ export async function copy(source: string, destination: string, options: CopyOpt interface CopyAllOptions { select?: string[]; dot?: boolean; - time?: string | number | Date; + time?: Date; } export async function copyAll( @@ -161,7 +161,7 @@ export async function copyAll( destination: string, options: CopyAllOptions = {} ) { - const { select = ['**/*'], dot = false, time = Date.now() } = options; + const { select = ['**/*'], dot = false, time = new Date() } = options; assertAbsolute(sourceDir); assertAbsolute(destination); diff --git a/src/dev/build/lib/integration_tests/fs.test.ts b/src/dev/build/lib/integration_tests/fs.test.ts index 6052924f34921..fa8a534d6e6b5 100644 --- a/src/dev/build/lib/integration_tests/fs.test.ts +++ b/src/dev/build/lib/integration_tests/fs.test.ts @@ -244,6 +244,20 @@ describe('copyAll()', () => { expect(Math.abs(fooDir.atimeMs - time.getTime())).toBeLessThan(oneDay); expect(Math.abs(barTxt.mtimeMs - time.getTime())).toBeLessThan(oneDay); }); + + it('defaults atime and mtime to now', async () => { + const destination = resolve(TMP, 'a/b/c/d/e/f'); + await copyAll(FIXTURES, destination); + const barTxt = statSync(resolve(destination, 'foo_dir/bar.txt')); + const fooDir = statSync(resolve(destination, 'foo_dir')); + + // precision is platform specific + const now = new Date(); + const oneDay = 86400000; + expect(Math.abs(barTxt.atimeMs - now.getTime())).toBeLessThan(oneDay); + expect(Math.abs(fooDir.atimeMs - now.getTime())).toBeLessThan(oneDay); + expect(Math.abs(barTxt.mtimeMs - now.getTime())).toBeLessThan(oneDay); + }); }); describe('getFileHash()', () => { From 1236834dd446db828127b86791895cb07f30664f Mon Sep 17 00:00:00 2001 From: DanielHabenicht Date: Thu, 21 Jan 2021 17:59:08 +0100 Subject: [PATCH 59/83] add enterpriseSearch.host (#88587) part of #76669 (cherry picked from commit f5c346cf1ebd22ba38d6b3058099b96dfbf4d7a7) --- docs/setup/settings.asciidoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 6dd76f782d668..26f095c59c644 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -205,6 +205,9 @@ 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 {es}, which is proxied through the {kib} server. +| `enterpriseSearch.host` + | The URL of your Enterprise Search instance + | `interpreter.enableInVisualize` | Enables use of interpreter in Visualize. *Default: `true`* From ed811e332def6e19c5e7bcc3ed719394e3da3b72 Mon Sep 17 00:00:00 2001 From: Constance Date: Thu, 21 Jan 2021 09:01:42 -0800 Subject: [PATCH 60/83] [Workplace Search] Update routes to use new encodePathParams helper (#88899) * Fix createRequest typing to correctly report errors if incorrect args are passed + simplify out generic which was causing problems w/ checking - I'd rather check for unnecessary args than hasValidData, which we're not using much anymore * Update WS settings routes * Update WS groups routes * Update WS sources routes --- .../lib/enterprise_search_request_handler.ts | 12 +- .../routes/workplace_search/groups.test.ts | 201 ++--- .../server/routes/workplace_search/groups.ts | 78 +- .../routes/workplace_search/settings.test.ts | 62 +- .../routes/workplace_search/settings.ts | 26 +- .../routes/workplace_search/sources.test.ts | 826 +++++++----------- .../server/routes/workplace_search/sources.ts | 390 ++++----- 7 files changed, 587 insertions(+), 1008 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts index a626198ad9c4d..d337854ffc2c7 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts @@ -20,10 +20,10 @@ interface ConstructorDependencies { config: ConfigType; log: Logger; } -interface RequestParams { +interface RequestParams { path: string; params?: object; - hasValidData?: (body?: ResponseBody) => boolean; + hasValidData?: Function; } interface ErrorResponse { message: string; @@ -32,7 +32,7 @@ interface ErrorResponse { }; } export interface IEnterpriseSearchRequestHandler { - createRequest(requestParams?: object): RequestHandler; + createRequest(requestParams?: RequestParams): RequestHandler; } /** @@ -53,11 +53,7 @@ export class EnterpriseSearchRequestHandler { this.enterpriseSearchUrl = config.host as string; } - createRequest({ - path, - params = {}, - hasValidData = () => true, - }: RequestParams) { + createRequest({ path, params = {}, hasValidData = () => true }: RequestParams) { return async ( _context: RequestHandlerContext, request: KibanaRequest, diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts index 2f244022be037..d7938e6eb7385 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts @@ -22,9 +22,6 @@ describe('groups routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/groups', @@ -35,7 +32,9 @@ describe('groups routes', () => { ...mockDependencies, router: mockRouter.router, }); + }); + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/groups', }); @@ -60,16 +59,19 @@ describe('groups routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - body: { - group_name: 'group', - }, - }; - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/groups', - ...mockRequest, + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + group_name: 'group', + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -92,24 +94,8 @@ describe('groups routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - body: { - page: { - current: 1, - size: 1, - }, - search: { - query: 'foo', - content_source_ids: ['123', '234'], - user_ids: ['345', '456'], - }, - }, - }; - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/groups/search', - ...mockRequest, }); }); @@ -150,30 +136,20 @@ describe('groups routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/groups/{id}', - payload: 'params', }); registerGroupRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/groups/123', + path: '/ws/org/groups/:id', }); }); }); @@ -183,15 +159,6 @@ describe('groups routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - const mockPayload = { - group: { - name: 'group', - }, - }; - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'put', path: '/api/workplace_search/groups/{id}', @@ -202,19 +169,24 @@ describe('groups routes', () => { ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - body: mockPayload, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/groups/123', - body: mockPayload, + path: '/ws/org/groups/:id', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + group: { + name: 'group', + }, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -227,7 +199,6 @@ describe('groups routes', () => { mockRouter = new MockRouter({ method: 'delete', path: '/api/workplace_search/groups/{id}', - payload: 'params', }); registerGroupRoute({ @@ -237,16 +208,8 @@ describe('groups routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/groups/123', + path: '/ws/org/groups/:id', }); }); }); @@ -256,30 +219,20 @@ describe('groups routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/groups/{id}/group_users', - payload: 'params', }); registerGroupUsersRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/groups/123/group_users', + path: '/ws/org/groups/:id/group_users', }); }); }); @@ -302,18 +255,20 @@ describe('groups routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { id: '123' }, - body: { - content_source_ids: ['123', '234'], - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/groups/123/share', - body: mockRequest.body, + path: '/ws/org/groups/:id/share', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + params: { id: '123' }, + body: { + content_source_ids: ['123', '234'], + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -336,18 +291,20 @@ describe('groups routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { id: '123' }, - body: { - user_ids: ['123', '234'], - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/groups/123/assign', - body: mockRequest.body, + path: '/ws/org/groups/:id/assign', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + params: { id: '123' }, + body: { + user_ids: ['123', '234'], + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -357,15 +314,6 @@ describe('groups routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - const mockPayload = { - group: { - content_source_boosts: [['boost'], ['boost2', 'boost3']], - }, - }; - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'put', path: '/api/workplace_search/groups/{id}/boosts', @@ -376,19 +324,22 @@ describe('groups routes', () => { ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - body: mockPayload, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/groups/123/update_source_boosts', - body: mockPayload, + path: '/ws/org/groups/:id/update_source_boosts', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + content_source_boosts: [['boost'], ['boost2', 'boost3']], + }, + }; + mockRouter.shouldValidate(request); }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts index ed75b0d6a91c8..4c3e8fa87fe29 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts @@ -28,12 +28,9 @@ export function registerGroupsRoute({ router, enterpriseSearchRequestHandler }: }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/org/groups', - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/groups', + }) ); } @@ -58,12 +55,9 @@ export function registerSearchGroupsRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/org/groups/search', - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/groups/search', + }) ); } @@ -77,11 +71,9 @@ export function registerGroupRoute({ router, enterpriseSearchRequestHandler }: R }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/groups/${request.params.id}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/groups/:id', + }) ); router.put( @@ -98,12 +90,9 @@ export function registerGroupRoute({ router, enterpriseSearchRequestHandler }: R }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/groups/${request.params.id}`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/groups/:id', + }) ); router.delete( @@ -115,11 +104,9 @@ export function registerGroupRoute({ router, enterpriseSearchRequestHandler }: R }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/groups/${request.params.id}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/groups/:id', + }) ); } @@ -136,11 +123,9 @@ export function registerGroupUsersRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/groups/${request.params.id}/group_users`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/groups/:id/group_users', + }) ); } @@ -160,12 +145,9 @@ export function registerShareGroupRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/groups/${request.params.id}/share`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/groups/:id/share', + }) ); } @@ -185,12 +167,9 @@ export function registerAssignGroupRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/groups/${request.params.id}/assign`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/groups/:id/assign', + }) ); } @@ -212,12 +191,9 @@ export function registerBoostsGroupRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/groups/${request.params.id}/update_source_boosts`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/groups/:id/update_source_boosts', + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/settings.test.ts index 932bf5e3685e6..db21d9ae78240 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/settings.test.ts @@ -18,22 +18,18 @@ describe('settings routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/org/settings', - payload: 'params', }); registerOrgSettingsRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - mockRouter.callRoute({}); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/settings', }); @@ -45,9 +41,6 @@ describe('settings routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'put', path: '/api/workplace_search/org/settings/customize', @@ -58,18 +51,18 @@ describe('settings routes', () => { ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - body: { - name: 'foo', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/settings/customize', - body: mockRequest.body, + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { body: { name: 'foo' } }; + mockRouter.shouldValidate(request); }); }); }); @@ -79,9 +72,6 @@ describe('settings routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'put', path: '/api/workplace_search/org/settings/oauth_application', @@ -92,22 +82,26 @@ describe('settings routes', () => { ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - body: { - oauth_application: { - name: 'foo', - confidential: true, - redirect_uri: 'http://foo.bar', - }, - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/settings/oauth_application', - body: mockRequest.body, + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + oauth_application: { + name: 'foo', + confidential: true, + redirect_uri: 'http://foo.bar', + }, + }, + }; + mockRouter.shouldValidate(request); }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/settings.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/settings.ts index cdba6609eb871..c05acff450402 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/settings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/settings.ts @@ -17,11 +17,9 @@ export function registerOrgSettingsRoute({ path: '/api/workplace_search/org/settings', validate: false, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/org/settings', - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/settings', + }) ); } @@ -38,12 +36,9 @@ export function registerOrgSettingsCustomizeRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/org/settings/customize', - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/settings/customize', + }) ); } @@ -64,12 +59,9 @@ export function registerOrgSettingsOauthApplicationRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/org/settings/oauth_application', - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/settings/oauth_application', + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts index d97a587e57ff2..9625d20d6a3cc 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts @@ -57,22 +57,18 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/account/sources', - payload: 'params', }); registerAccountSourcesRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - mockRouter.callRoute({}); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/sources', }); @@ -84,22 +80,18 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/account/sources/status', - payload: 'params', }); registerAccountSourcesStatusRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - mockRouter.callRoute({}); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/sources/status', }); @@ -111,30 +103,20 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/account/sources/{id}', - payload: 'params', }); registerAccountSourceRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123', + path: '/ws/sources/:id', }); }); }); @@ -147,7 +129,6 @@ describe('sources routes', () => { mockRouter = new MockRouter({ method: 'delete', path: '/api/workplace_search/account/sources/{id}', - payload: 'params', }); registerAccountSourceRoute({ @@ -157,16 +138,8 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123', + path: '/ws/sources/:id', }); }); }); @@ -189,22 +162,24 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - body: { - service_type: 'google', - name: 'Google', - login: 'user', - password: 'changeme', - organizations: 'swiftype', - indexPermissions: true, - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/sources/form_create', - body: mockRequest.body, + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + service_type: 'google', + name: 'Google', + login: 'user', + password: 'changeme', + organizations: ['swiftype'], + indexPermissions: true, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -227,24 +202,25 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { id: '123' }, - body: { - query: 'foo', - page: { - current: 1, - size: 10, - total_pages: 1, - total_results: 10, - }, - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/documents', - body: mockRequest.body, + path: '/ws/sources/:id/documents', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + query: 'foo', + page: { + current: 1, + size: 10, + total_pages: 1, + total_results: 10, + }, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -254,30 +230,20 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/account/sources/{id}/federated_summary', - payload: 'params', }); registerAccountSourceFederatedSummaryRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/federated_summary', + path: '/ws/sources/:id/federated_summary', }); }); }); @@ -287,30 +253,20 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/account/sources/{id}/reauth_prepare', - payload: 'params', }); registerAccountSourceReauthPrepareRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/reauth_prepare', + path: '/ws/sources/:id/reauth_prepare', }); }); }); @@ -333,20 +289,21 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { id: '123' }, - body: { - content_source: { - name: 'foo', - }, - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/settings', - body: mockRequest.body, + path: '/ws/sources/:id/settings', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + content_source: { + name: 'foo', + }, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -356,63 +313,43 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/account/pre_sources/{id}', - payload: 'params', }); registerAccountPreSourceRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/pre_content_sources/123', + path: '/ws/pre_content_sources/:id', }); }); }); - describe('GET /api/workplace_search/account/sources/{service_type}/prepare', () => { + describe('GET /api/workplace_search/account/sources/{serviceType}/prepare', () => { let mockRouter: MockRouter; beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', - path: '/api/workplace_search/account/sources/{service_type}/prepare', - payload: 'params', + path: '/api/workplace_search/account/sources/{serviceType}/prepare', }); registerAccountPrepareSourcesRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - service_type: 'zendesk', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/zendesk/prepare', + path: '/ws/sources/:serviceType/prepare', }); }); }); @@ -422,9 +359,6 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'put', path: '/api/workplace_search/account/sources/{id}/searchable', @@ -435,21 +369,22 @@ describe('sources routes', () => { ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - body: { - searchable: true, - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/searchable', - body: mockRequest.body, + path: '/ws/sources/:id/searchable', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + searchable: true, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -459,30 +394,20 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/account/sources/{id}/display_settings/config', - payload: 'params', }); registerAccountSourceDisplaySettingsConfig({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/display_settings/config', + path: '/ws/sources/:id/display_settings/config', }); }); }); @@ -505,26 +430,28 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { id: '123' }, - body: { - titleField: 'foo', - subtitleField: 'bar', - descriptionField: 'this is a thing', - urlField: 'http://youknowfor.search', - color: '#aaa', - detailFields: { - fieldName: 'myField', - label: 'My Field', - }, - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/display_settings/config', - body: mockRequest.body, + path: '/ws/sources/:id/display_settings/config', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + titleField: 'foo', + subtitleField: 'bar', + descriptionField: 'this is a thing', + urlField: 'http://youknowfor.search', + urlFieldIsLinkable: true, + color: '#aaa', + detailFields: { + fieldName: 'myField', + label: 'My Field', + }, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -534,30 +461,20 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/account/sources/{id}/schemas', - payload: 'params', }); registerAccountSourceSchemasRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/schemas', + path: '/ws/sources/:id/schemas', }); }); }); @@ -580,84 +497,70 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { id: '123' }, - body: {}, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/schemas', - body: mockRequest.body, + path: '/ws/sources/:id/schemas', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { body: { someSchemaKey: 'text' } }; + mockRouter.shouldValidate(request); }); }); }); - describe('GET /api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', () => { + describe('GET /api/workplace_search/account/sources/{sourceId}/reindex_job/{jobId}', () => { let mockRouter: MockRouter; beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', - path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', - payload: 'params', + path: '/api/workplace_search/account/sources/{sourceId}/reindex_job/{jobId}', }); registerAccountSourceReindexJobRoute({ ...mockDependencies, router: mockRouter.router, }); + }); + it('creates a request handler', () => { const mockRequest = { params: { - source_id: '123', - job_id: '345', + sourceId: '123', + jobId: '345', }, }; mockRouter.callRoute(mockRequest); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/reindex_job/345', + path: '/ws/sources/:sourceId/reindex_job/:jobId', }); }); }); - describe('GET /api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', () => { + describe('GET /api/workplace_search/account/sources/{sourceId}/reindex_job/{jobId}/status', () => { let mockRouter: MockRouter; beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', - path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', - payload: 'params', + path: '/api/workplace_search/account/sources/{sourceId}/reindex_job/{jobId}/status', }); registerAccountSourceReindexJobStatusRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - source_id: '123', - job_id: '345', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/sources/123/reindex_job/345/status', + path: '/ws/sources/:sourceId/reindex_job/:jobId/status', }); }); }); @@ -667,22 +570,18 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/org/sources', - payload: 'params', }); registerOrgSourcesRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - mockRouter.callRoute({}); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/sources', }); @@ -694,22 +593,18 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/org/sources/status', - payload: 'params', }); registerOrgSourcesStatusRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - mockRouter.callRoute({}); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/sources/status', }); @@ -721,30 +616,20 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/org/sources/{id}', - payload: 'params', }); registerOrgSourceRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123', + path: '/ws/org/sources/:id', }); }); }); @@ -757,7 +642,6 @@ describe('sources routes', () => { mockRouter = new MockRouter({ method: 'delete', path: '/api/workplace_search/org/sources/{id}', - payload: 'params', }); registerOrgSourceRoute({ @@ -767,16 +651,8 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123', + path: '/ws/org/sources/:id', }); }); }); @@ -799,22 +675,24 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - body: { - service_type: 'google', - name: 'Google', - login: 'user', - password: 'changeme', - organizations: 'swiftype', - indexPermissions: true, - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/sources/form_create', - body: mockRequest.body, + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + service_type: 'google', + name: 'Google', + login: 'user', + password: 'changeme', + organizations: ['swiftype'], + indexPermissions: true, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -837,24 +715,25 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { id: '123' }, - body: { - query: 'foo', - page: { - current: 1, - size: 10, - total_pages: 1, - total_results: 10, - }, - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/documents', - body: mockRequest.body, + path: '/ws/org/sources/:id/documents', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + query: 'foo', + page: { + current: 1, + size: 10, + total_pages: 1, + total_results: 10, + }, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -864,30 +743,20 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/org/sources/{id}/federated_summary', - payload: 'params', }); registerOrgSourceFederatedSummaryRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/federated_summary', + path: '/ws/org/sources/:id/federated_summary', }); }); }); @@ -897,30 +766,20 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/org/sources/{id}/reauth_prepare', - payload: 'params', }); registerOrgSourceReauthPrepareRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/reauth_prepare', + path: '/ws/org/sources/:id/reauth_prepare', }); }); }); @@ -943,20 +802,21 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { id: '123' }, - body: { - content_source: { - name: 'foo', - }, - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/settings', - body: mockRequest.body, + path: '/ws/org/sources/:id/settings', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + content_source: { + name: 'foo', + }, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -966,63 +826,43 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/org/pre_sources/{id}', - payload: 'params', }); registerOrgPreSourceRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/pre_content_sources/123', + path: '/ws/org/pre_content_sources/:id', }); }); }); - describe('GET /api/workplace_search/org/sources/{service_type}/prepare', () => { + describe('GET /api/workplace_search/org/sources/{serviceType}/prepare', () => { let mockRouter: MockRouter; beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', - path: '/api/workplace_search/org/sources/{service_type}/prepare', - payload: 'params', + path: '/api/workplace_search/org/sources/{serviceType}/prepare', }); registerOrgPrepareSourcesRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - service_type: 'zendesk', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/zendesk/prepare', + path: '/ws/org/sources/:serviceType/prepare', }); }); }); @@ -1032,9 +872,6 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'put', path: '/api/workplace_search/org/sources/{id}/searchable', @@ -1045,21 +882,22 @@ describe('sources routes', () => { ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - body: { - searchable: true, - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/searchable', - body: mockRequest.body, + path: '/ws/org/sources/:id/searchable', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + searchable: true, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -1069,30 +907,20 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/org/sources/{id}/display_settings/config', - payload: 'params', }); registerOrgSourceDisplaySettingsConfig({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/display_settings/config', + path: '/ws/org/sources/:id/display_settings/config', }); }); }); @@ -1115,26 +943,28 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { id: '123' }, - body: { - titleField: 'foo', - subtitleField: 'bar', - descriptionField: 'this is a thing', - urlField: 'http://youknowfor.search', - color: '#aaa', - detailFields: { - fieldName: 'myField', - label: 'My Field', - }, - }, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/display_settings/config', - body: mockRequest.body, + path: '/ws/org/sources/:id/display_settings/config', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + titleField: 'foo', + subtitleField: 'bar', + descriptionField: 'this is a thing', + urlField: 'http://youknowfor.search', + urlFieldIsLinkable: true, + color: '#aaa', + detailFields: { + fieldName: 'myField', + label: 'My Field', + }, + }, + }; + mockRouter.shouldValidate(request); }); }); }); @@ -1144,30 +974,20 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/org/sources/{id}/schemas', - payload: 'params', }); registerOrgSourceSchemasRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - id: '123', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/schemas', + path: '/ws/org/sources/:id/schemas', }); }); }); @@ -1190,84 +1010,61 @@ describe('sources routes', () => { }); it('creates a request handler', () => { - const mockRequest = { - params: { id: '123' }, - body: {}, - }; - - mockRouter.callRoute(mockRequest); - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/schemas', - body: mockRequest.body, + path: '/ws/org/sources/:id/schemas', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { body: { someSchemaKey: 'number' } }; + mockRouter.shouldValidate(request); }); }); }); - describe('GET /api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', () => { + describe('GET /api/workplace_search/org/sources/{sourceId}/reindex_job/{jobId}', () => { let mockRouter: MockRouter; beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', - path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', - payload: 'params', + path: '/api/workplace_search/org/sources/{sourceId}/reindex_job/{jobId}', }); registerOrgSourceReindexJobRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - source_id: '123', - job_id: '345', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/reindex_job/345', + path: '/ws/org/sources/:sourceId/reindex_job/:jobId', }); }); }); - describe('GET /api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', () => { + describe('GET /api/workplace_search/org/sources/{sourceId}/reindex_job/{jobId}/status', () => { let mockRouter: MockRouter; beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', - path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', - payload: 'params', + path: '/api/workplace_search/org/sources/{sourceId}/reindex_job/{jobId}/status', }); registerOrgSourceReindexJobStatusRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - source_id: '123', - job_id: '345', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/sources/123/reindex_job/345/status', + path: '/ws/org/sources/:sourceId/reindex_job/:jobId/status', }); }); }); @@ -1277,9 +1074,6 @@ describe('sources routes', () => { beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', path: '/api/workplace_search/org/settings/connectors', @@ -1289,59 +1083,46 @@ describe('sources routes', () => { ...mockDependencies, router: mockRouter.router, }); + }); - mockRouter.callRoute({}); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/settings/connectors', }); }); }); - describe('GET /api/workplace_search/org/settings/connectors/{service_type}', () => { + describe('GET /api/workplace_search/org/settings/connectors/{serviceType}', () => { let mockRouter: MockRouter; beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'get', - path: '/api/workplace_search/org/settings/connectors/{service_type}', - payload: 'params', + path: '/api/workplace_search/org/settings/connectors/{serviceType}', }); registerOrgSourceOauthConfigurationRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - service_type: 'zendesk', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/settings/connectors/zendesk', + path: '/ws/org/settings/connectors/:serviceType', }); }); }); - describe('POST /api/workplace_search/org/settings/connectors/{service_type}', () => { + describe('POST /api/workplace_search/org/settings/connectors/{serviceType}', () => { let mockRouter: MockRouter; beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'post', - path: '/api/workplace_search/org/settings/connectors/{service_type}', + path: '/api/workplace_search/org/settings/connectors/{serviceType}', payload: 'body', }); @@ -1349,34 +1130,30 @@ describe('sources routes', () => { ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - service_type: 'zendesk', - }, - body: mockConfig, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/settings/connectors/zendesk', - body: mockRequest.body, + path: '/ws/org/settings/connectors/:serviceType', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { body: mockConfig }; + mockRouter.shouldValidate(request); }); }); }); - describe('PUT /api/workplace_search/org/settings/connectors/{service_type}', () => { + describe('PUT /api/workplace_search/org/settings/connectors/{serviceType}', () => { let mockRouter: MockRouter; beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'put', - path: '/api/workplace_search/org/settings/connectors/{service_type}', + path: '/api/workplace_search/org/settings/connectors/{serviceType}', payload: 'body', }); @@ -1384,52 +1161,41 @@ describe('sources routes', () => { ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - service_type: 'zendesk', - }, - body: mockConfig, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/settings/connectors/zendesk', - body: mockRequest.body, + path: '/ws/org/settings/connectors/:serviceType', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { body: mockConfig }; + mockRouter.shouldValidate(request); }); }); }); - describe('DELETE /api/workplace_search/org/settings/connectors/{service_type}', () => { + describe('DELETE /api/workplace_search/org/settings/connectors/{serviceType}', () => { let mockRouter: MockRouter; beforeEach(() => { jest.clearAllMocks(); - }); - - it('creates a request handler', () => { mockRouter = new MockRouter({ method: 'delete', - path: '/api/workplace_search/org/settings/connectors/{service_type}', - payload: 'params', + path: '/api/workplace_search/org/settings/connectors/{serviceType}', }); registerOrgSourceOauthConfigurationRoute({ ...mockDependencies, router: mockRouter.router, }); + }); - const mockRequest = { - params: { - service_type: 'zendesk', - }, - }; - - mockRouter.callRoute(mockRequest); - + it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/ws/org/settings/connectors/zendesk', + path: '/ws/org/settings/connectors/:serviceType', }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index 04db6bbc2912e..a2f950a54471e 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -59,11 +59,9 @@ export function registerAccountSourcesRoute({ path: '/api/workplace_search/account/sources', validate: false, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/sources', - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources', + }) ); } @@ -76,11 +74,9 @@ export function registerAccountSourcesStatusRoute({ path: '/api/workplace_search/account/sources/status', validate: false, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/sources/status', - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/status', + }) ); } @@ -97,11 +93,9 @@ export function registerAccountSourceRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id', + }) ); router.delete( @@ -113,11 +107,9 @@ export function registerAccountSourceRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id', + }) ); } @@ -139,12 +131,9 @@ export function registerAccountCreateSourceRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/sources/form_create', - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/form_create', + }) ); } @@ -165,12 +154,9 @@ export function registerAccountSourceDocumentsRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}/documents`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id/documents', + }) ); } @@ -187,11 +173,9 @@ export function registerAccountSourceFederatedSummaryRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}/federated_summary`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id/federated_summary', + }) ); } @@ -208,11 +192,9 @@ export function registerAccountSourceReauthPrepareRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}/reauth_prepare`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id/reauth_prepare', + }) ); } @@ -234,12 +216,9 @@ export function registerAccountSourceSettingsRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}/settings`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id/settings', + }) ); } @@ -256,11 +235,9 @@ export function registerAccountPreSourceRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/pre_content_sources/${request.params.id}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/pre_content_sources/:id', + }) ); } @@ -270,18 +247,16 @@ export function registerAccountPrepareSourcesRoute({ }: RouteDependencies) { router.get( { - path: '/api/workplace_search/account/sources/{service_type}/prepare', + path: '/api/workplace_search/account/sources/{serviceType}/prepare', validate: { params: schema.object({ - service_type: schema.string(), + serviceType: schema.string(), }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.service_type}/prepare`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:serviceType/prepare', + }) ); } @@ -301,12 +276,9 @@ export function registerAccountSourceSearchableRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}/searchable`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id/searchable', + }) ); } @@ -323,11 +295,9 @@ export function registerAccountSourceDisplaySettingsConfig({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}/display_settings/config`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id/display_settings/config', + }) ); router.post( @@ -340,12 +310,9 @@ export function registerAccountSourceDisplaySettingsConfig({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}/display_settings/config`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id/display_settings/config', + }) ); } @@ -362,11 +329,9 @@ export function registerAccountSourceSchemasRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}/schemas`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id/schemas', + }) ); router.post( @@ -379,12 +344,9 @@ export function registerAccountSourceSchemasRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.id}/schemas`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:id/schemas', + }) ); } @@ -394,19 +356,17 @@ export function registerAccountSourceReindexJobRoute({ }: RouteDependencies) { router.get( { - path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', + path: '/api/workplace_search/account/sources/{sourceId}/reindex_job/{jobId}', validate: { params: schema.object({ - source_id: schema.string(), - job_id: schema.string(), + sourceId: schema.string(), + jobId: schema.string(), }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.source_id}/reindex_job/${request.params.job_id}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:sourceId/reindex_job/:jobId', + }) ); } @@ -416,19 +376,17 @@ export function registerAccountSourceReindexJobStatusRoute({ }: RouteDependencies) { router.get( { - path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', + path: '/api/workplace_search/account/sources/{sourceId}/reindex_job/{jobId}/status', validate: { params: schema.object({ - source_id: schema.string(), - job_id: schema.string(), + sourceId: schema.string(), + jobId: schema.string(), }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/sources/${request.params.source_id}/reindex_job/${request.params.job_id}/status`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:sourceId/reindex_job/:jobId/status', + }) ); } @@ -441,11 +399,9 @@ export function registerOrgSourcesRoute({ path: '/api/workplace_search/org/sources', validate: false, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/org/sources', - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources', + }) ); } @@ -458,11 +414,9 @@ export function registerOrgSourcesStatusRoute({ path: '/api/workplace_search/org/sources/status', validate: false, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/org/sources/status', - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/status', + }) ); } @@ -479,11 +433,9 @@ export function registerOrgSourceRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id', + }) ); router.delete( @@ -495,11 +447,9 @@ export function registerOrgSourceRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id', + }) ); } @@ -521,12 +471,9 @@ export function registerOrgCreateSourceRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/org/sources/form_create', - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/form_create', + }) ); } @@ -547,12 +494,9 @@ export function registerOrgSourceDocumentsRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}/documents`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id/documents', + }) ); } @@ -569,11 +513,9 @@ export function registerOrgSourceFederatedSummaryRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}/federated_summary`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id/federated_summary', + }) ); } @@ -590,11 +532,9 @@ export function registerOrgSourceReauthPrepareRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}/reauth_prepare`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id/reauth_prepare', + }) ); } @@ -616,12 +556,9 @@ export function registerOrgSourceSettingsRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}/settings`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id/settings', + }) ); } @@ -638,11 +575,9 @@ export function registerOrgPreSourceRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/pre_content_sources/${request.params.id}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/pre_content_sources/:id', + }) ); } @@ -652,18 +587,16 @@ export function registerOrgPrepareSourcesRoute({ }: RouteDependencies) { router.get( { - path: '/api/workplace_search/org/sources/{service_type}/prepare', + path: '/api/workplace_search/org/sources/{serviceType}/prepare', validate: { params: schema.object({ - service_type: schema.string(), + serviceType: schema.string(), }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.service_type}/prepare`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:serviceType/prepare', + }) ); } @@ -683,12 +616,9 @@ export function registerOrgSourceSearchableRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}/searchable`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id/searchable', + }) ); } @@ -705,11 +635,9 @@ export function registerOrgSourceDisplaySettingsConfig({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}/display_settings/config`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id/display_settings/config', + }) ); router.post( @@ -722,12 +650,9 @@ export function registerOrgSourceDisplaySettingsConfig({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}/display_settings/config`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id/display_settings/config', + }) ); } @@ -744,11 +669,9 @@ export function registerOrgSourceSchemasRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}/schemas`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id/schemas', + }) ); router.post( @@ -761,12 +684,9 @@ export function registerOrgSourceSchemasRoute({ }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.id}/schemas`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:id/schemas', + }) ); } @@ -776,19 +696,17 @@ export function registerOrgSourceReindexJobRoute({ }: RouteDependencies) { router.get( { - path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', + path: '/api/workplace_search/org/sources/{sourceId}/reindex_job/{jobId}', validate: { params: schema.object({ - source_id: schema.string(), - job_id: schema.string(), + sourceId: schema.string(), + jobId: schema.string(), }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.source_id}/reindex_job/${request.params.job_id}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:sourceId/reindex_job/:jobId', + }) ); } @@ -798,19 +716,17 @@ export function registerOrgSourceReindexJobStatusRoute({ }: RouteDependencies) { router.get( { - path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', + path: '/api/workplace_search/org/sources/{sourceId}/reindex_job/{jobId}/status', validate: { params: schema.object({ - source_id: schema.string(), - job_id: schema.string(), + sourceId: schema.string(), + jobId: schema.string(), }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/sources/${request.params.source_id}/reindex_job/${request.params.job_id}/status`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:sourceId/reindex_job/:jobId/status', + }) ); } @@ -823,11 +739,9 @@ export function registerOrgSourceOauthConfigurationsRoute({ path: '/api/workplace_search/org/settings/connectors', validate: false, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: '/ws/org/settings/connectors', - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/settings/connectors', + }) ); } @@ -837,70 +751,60 @@ export function registerOrgSourceOauthConfigurationRoute({ }: RouteDependencies) { router.get( { - path: '/api/workplace_search/org/settings/connectors/{service_type}', + path: '/api/workplace_search/org/settings/connectors/{serviceType}', validate: { params: schema.object({ - service_type: schema.string(), + serviceType: schema.string(), }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/settings/connectors/${request.params.service_type}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/settings/connectors/:serviceType', + }) ); router.post( { - path: '/api/workplace_search/org/settings/connectors/{service_type}', + path: '/api/workplace_search/org/settings/connectors/{serviceType}', validate: { params: schema.object({ - service_type: schema.string(), + serviceType: schema.string(), }), body: oAuthConfigSchema, }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/settings/connectors/${request.params.service_type}`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/settings/connectors/:serviceType', + }) ); router.put( { - path: '/api/workplace_search/org/settings/connectors/{service_type}', + path: '/api/workplace_search/org/settings/connectors/{serviceType}', validate: { params: schema.object({ - service_type: schema.string(), + serviceType: schema.string(), }), body: oAuthConfigSchema, }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/settings/connectors/${request.params.service_type}`, - body: request.body, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/settings/connectors/:serviceType', + }) ); router.delete( { - path: '/api/workplace_search/org/settings/connectors/{service_type}', + path: '/api/workplace_search/org/settings/connectors/{serviceType}', validate: { params: schema.object({ - service_type: schema.string(), + serviceType: schema.string(), }), }, }, - async (context, request, response) => { - return enterpriseSearchRequestHandler.createRequest({ - path: `/ws/org/settings/connectors/${request.params.service_type}`, - })(context, request, response); - } + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/settings/connectors/:serviceType', + }) ); } From fde408545d09167a1994ac4b9c9b8040d064dc83 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 21 Jan 2021 18:16:04 +0100 Subject: [PATCH 61/83] decorateSnapshotUi: get file from stacktrace (#88950) --- package.json | 1 + .../snapshots/decorate_snapshot_ui.test.ts | 203 +++++++++++++----- .../lib/snapshots/decorate_snapshot_ui.ts | 157 ++++++-------- .../monitor_states_real_data.snap | 2 +- .../services/__snapshots__/throughput.snap | 2 +- .../traces/__snapshots__/top_traces.snap | 2 +- .../transactions/__snapshots__/breakdown.snap | 4 +- .../__snapshots__/error_rate.snap | 2 +- .../__snapshots__/top_transaction_groups.snap | 2 +- .../csm/__snapshots__/page_load_dist.snap | 8 +- .../tests/csm/__snapshots__/page_views.snap | 8 +- .../__snapshots__/service_maps.snap | 6 +- .../transactions/__snapshots__/latency.snap | 6 +- yarn.lock | 5 + 14 files changed, 240 insertions(+), 168 deletions(-) diff --git a/package.json b/package.json index ff6df054be220..87e0f84695235 100644 --- a/package.json +++ b/package.json @@ -592,6 +592,7 @@ "base64-js": "^1.3.1", "base64url": "^3.0.1", "broadcast-channel": "^3.0.3", + "callsites": "^3.1.0", "chai": "3.5.0", "chance": "1.0.18", "chromedriver": "^87.0.3", diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts index a138673d69ebf..2a238cdeb5385 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts @@ -12,7 +12,32 @@ import { decorateSnapshotUi, expectSnapshot } from './decorate_snapshot_ui'; import path from 'path'; import fs from 'fs'; +const createMockTest = ({ + title = 'Test', + passed = true, +}: { title?: string; passed?: boolean } = {}) => { + return { + fullTitle: () => title, + isPassed: () => passed, + parent: {}, + } as Test; +}; + describe('decorateSnapshotUi', () => { + const snapshotFolder = path.resolve(__dirname, '__snapshots__'); + const snapshotFile = path.resolve(snapshotFolder, 'decorate_snapshot_ui.test.snap'); + + const cleanup = () => { + if (fs.existsSync(snapshotFile)) { + fs.unlinkSync(snapshotFile); + fs.rmdirSync(snapshotFolder); + } + }; + + beforeEach(cleanup); + + afterAll(cleanup); + describe('when running a test', () => { let lifecycle: Lifecycle; beforeEach(() => { @@ -21,15 +46,7 @@ describe('decorateSnapshotUi', () => { }); it('passes when the snapshot matches the actual value', async () => { - const test: Test = { - title: 'Test', - file: 'foo.ts', - parent: { - file: 'foo.ts', - tests: [], - suites: [], - }, - } as any; + const test = createMockTest(); await lifecycle.beforeEachTest.trigger(test); @@ -39,15 +56,7 @@ describe('decorateSnapshotUi', () => { }); it('throws when the snapshot does not match the actual value', async () => { - const test: Test = { - title: 'Test', - file: 'foo.ts', - parent: { - file: 'foo.ts', - tests: [], - suites: [], - }, - } as any; + const test = createMockTest(); await lifecycle.beforeEachTest.trigger(test); @@ -57,27 +66,10 @@ describe('decorateSnapshotUi', () => { }); it('writes a snapshot to an external file if it does not exist', async () => { - const test: Test = { - title: 'Test', - file: __filename, - isPassed: () => true, - } as any; - - // @ts-expect-error - test.parent = { - file: __filename, - tests: [test], - suites: [], - }; + const test: Test = createMockTest(); await lifecycle.beforeEachTest.trigger(test); - const snapshotFile = path.resolve( - __dirname, - '__snapshots__', - 'decorate_snapshot_ui.test.snap' - ); - expect(fs.existsSync(snapshotFile)).toBe(false); expect(() => { @@ -87,10 +79,48 @@ describe('decorateSnapshotUi', () => { await lifecycle.afterTestSuite.trigger(test.parent); expect(fs.existsSync(snapshotFile)).toBe(true); + }); + }); - fs.unlinkSync(snapshotFile); + describe('when writing multiple snapshots to a single file', () => { + let lifecycle: Lifecycle; + beforeEach(() => { + lifecycle = new Lifecycle(); + decorateSnapshotUi({ lifecycle, updateSnapshots: false, isCi: false }); + }); + + beforeEach(() => { + fs.mkdirSync(path.resolve(__dirname, '__snapshots__')); + fs.writeFileSync( + snapshotFile, + `// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[\`Test1 1\`] = \`"foo"\`; + +exports[\`Test2 1\`] = \`"bar"\`; + `, + { encoding: 'utf-8' } + ); + }); + + it('compares to an existing snapshot', async () => { + const test1 = createMockTest({ title: 'Test1' }); + + await lifecycle.beforeEachTest.trigger(test1); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).not.toThrow(); + + const test2 = createMockTest({ title: 'Test2' }); - fs.rmdirSync(path.resolve(__dirname, '__snapshots__')); + await lifecycle.beforeEachTest.trigger(test2); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).toThrow(); + + await lifecycle.afterTestSuite.trigger(test1.parent); }); }); @@ -102,15 +132,7 @@ describe('decorateSnapshotUi', () => { }); it("doesn't throw if the value does not match", async () => { - const test: Test = { - title: 'Test', - file: 'foo.ts', - parent: { - file: 'foo.ts', - tests: [], - suites: [], - }, - } as any; + const test = createMockTest(); await lifecycle.beforeEachTest.trigger(test); @@ -128,15 +150,7 @@ describe('decorateSnapshotUi', () => { }); it('throws on new snapshots', async () => { - const test: Test = { - title: 'Test', - file: 'foo.ts', - parent: { - file: 'foo.ts', - tests: [], - suites: [], - }, - } as any; + const test = createMockTest(); await lifecycle.beforeEachTest.trigger(test); @@ -144,5 +158,82 @@ describe('decorateSnapshotUi', () => { expectSnapshot('bar').toMatchInline(); }).toThrow(); }); + + describe('when adding to an existing file', () => { + beforeEach(() => { + fs.mkdirSync(path.resolve(__dirname, '__snapshots__')); + fs.writeFileSync( + snapshotFile, + `// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[\`Test 1\`] = \`"foo"\`; + +exports[\`Test2 1\`] = \`"bar"\`; + `, + { encoding: 'utf-8' } + ); + }); + + it('does not throw on an existing test', async () => { + const test = createMockTest({ title: 'Test' }); + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).not.toThrow(); + }); + + it('throws on a new test', async () => { + const test = createMockTest({ title: 'New test' }); + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).toThrow(); + }); + + it('does not throw when all snapshots are used ', async () => { + const test = createMockTest({ title: 'Test' }); + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).not.toThrow(); + + const test2 = createMockTest({ title: 'Test2' }); + + await lifecycle.beforeEachTest.trigger(test2); + + expect(() => { + expectSnapshot('bar').toMatch(); + }).not.toThrow(); + + const afterTestSuite = lifecycle.afterTestSuite.trigger({}); + + await expect(afterTestSuite).resolves.toBe(undefined); + }); + + it('throws on unused snapshots', async () => { + const test = createMockTest({ title: 'Test' }); + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).not.toThrow(); + + const afterTestSuite = lifecycle.afterTestSuite.trigger({}); + + await expect(afterTestSuite).rejects.toMatchInlineSnapshot(` + [Error: 1 obsolete snapshot(s) found: + Test2 1. + + Run tests again with \`--updateSnapshots\` to remove them.] + `); + }); + }); }); }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index c43b50de3afd0..2111f1a6e5e90 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -15,9 +15,10 @@ import { import path from 'path'; import prettier from 'prettier'; import babelTraverse from '@babel/traverse'; -import { flatten, once } from 'lodash'; +import { once } from 'lodash'; +import callsites from 'callsites'; import { Lifecycle } from '../lifecycle'; -import { Test, Suite } from '../../fake_mocha_types'; +import { Test } from '../../fake_mocha_types'; type ISnapshotState = InstanceType; @@ -28,40 +29,17 @@ interface SnapshotContext { currentTestName: string; } -let testContext: { - file: string; - snapshotTitle: string; - snapshotContext: SnapshotContext; -} | null = null; - -let registered: boolean = false; - -function getSnapshotMeta(currentTest: Test) { - // Make sure snapshot title is unique per-file, rather than entire - // suite. This allows reuse of tests, for instance to compare - // results for different configurations. - - const titles = [currentTest.title]; - const file = currentTest.file; - - let test: Suite | undefined = currentTest?.parent; - - while (test && test.file === file) { - titles.push(test.title); - test = test.parent; - } - - const snapshotTitle = titles.reverse().join(' '); - - if (!file || !snapshotTitle) { - throw new Error(`file or snapshotTitle not available in Mocha test context`); - } - - return { - file, - snapshotTitle, - }; -} +const globalState: { + updateSnapshot: SnapshotUpdateState; + registered: boolean; + currentTest: Test | null; + snapshots: Array<{ tests: Test[]; file: string; snapshotState: ISnapshotState }>; +} = { + updateSnapshot: 'none', + registered: false, + currentTest: null, + snapshots: [], +}; const modifyStackTracePrepareOnce = once(() => { const originalPrepareStackTrace = Error.prepareStackTrace; @@ -72,7 +50,7 @@ const modifyStackTracePrepareOnce = once(() => { Error.prepareStackTrace = (error, structuredStackTrace) => { let filteredStrackTrace: NodeJS.CallSite[] = structuredStackTrace; - if (registered) { + if (globalState.registered) { filteredStrackTrace = filteredStrackTrace.filter((callSite) => { // check for both compiled and uncompiled files return !callSite.getFileName()?.match(/decorate_snapshot_ui\.(js|ts)/); @@ -94,21 +72,16 @@ export function decorateSnapshotUi({ updateSnapshots: boolean; isCi: boolean; }) { - let snapshotStatesByFilePath: Record< - string, - { snapshotState: ISnapshotState; testsInFile: Test[] } - > = {}; - - registered = true; - - let updateSnapshot: SnapshotUpdateState; + globalState.registered = true; + globalState.snapshots.length = 0; + globalState.currentTest = null; if (isCi) { // make sure snapshots that have not been committed // are not written to file on CI, passing the test - updateSnapshot = 'none'; + globalState.updateSnapshot = 'none'; } else { - updateSnapshot = updateSnapshots ? 'all' : 'new'; + globalState.updateSnapshot = updateSnapshots ? 'all' : 'new'; } modifyStackTracePrepareOnce(); @@ -125,21 +98,8 @@ export function decorateSnapshotUi({ // @ts-expect-error global.expectSnapshot = expectSnapshot; - lifecycle.beforeEachTest.add((currentTest: Test) => { - const { file, snapshotTitle } = getSnapshotMeta(currentTest); - - if (!snapshotStatesByFilePath[file]) { - snapshotStatesByFilePath[file] = getSnapshotState(file, currentTest, updateSnapshot); - } - - testContext = { - file, - snapshotTitle, - snapshotContext: { - snapshotState: snapshotStatesByFilePath[file].snapshotState, - currentTestName: snapshotTitle, - }, - }; + lifecycle.beforeEachTest.add((test: Test) => { + globalState.currentTest = test; }); lifecycle.afterTestSuite.add(function (testSuite) { @@ -150,19 +110,18 @@ export function decorateSnapshotUi({ const unused: string[] = []; - Object.keys(snapshotStatesByFilePath).forEach((file) => { - const { snapshotState, testsInFile } = snapshotStatesByFilePath[file]; - - testsInFile.forEach((test) => { - const snapshotMeta = getSnapshotMeta(test); + globalState.snapshots.forEach((snapshot) => { + const { tests, snapshotState } = snapshot; + tests.forEach((test) => { + const title = test.fullTitle(); // If test is failed or skipped, mark snapshots as used. Otherwise, // running a test in isolation will generate false positives. if (!test.isPassed()) { - snapshotState.markSnapshotsAsCheckedForTest(snapshotMeta.snapshotTitle); + snapshotState.markSnapshotsAsCheckedForTest(title); } }); - if (!updateSnapshots) { + if (globalState.updateSnapshot !== 'all') { unused.push(...snapshotState.getUncheckedKeys()); } else { snapshotState.removeUncheckedKeys(); @@ -179,28 +138,14 @@ export function decorateSnapshotUi({ ); } - snapshotStatesByFilePath = {}; + globalState.snapshots.length = 0; }); } -function recursivelyGetTestsFromSuite(suite: Suite): Test[] { - return suite.tests.concat(flatten(suite.suites.map((s) => recursivelyGetTestsFromSuite(s)))); -} - -function getSnapshotState(file: string, test: Test, updateSnapshot: SnapshotUpdateState) { +function getSnapshotState(file: string, updateSnapshot: SnapshotUpdateState) { const dirname = path.dirname(file); const filename = path.basename(file); - let parent: Suite | undefined = test.parent; - - while (parent && parent.parent?.file === file) { - parent = parent.parent; - } - - if (!parent) { - throw new Error('Top-level suite not found'); - } - const snapshotState = new SnapshotState( path.join(dirname + `/__snapshots__/` + filename.replace(path.extname(filename), '.snap')), { @@ -211,24 +156,54 @@ function getSnapshotState(file: string, test: Test, updateSnapshot: SnapshotUpda } ); - return { snapshotState, testsInFile: recursivelyGetTestsFromSuite(parent) }; + return snapshotState; } export function expectSnapshot(received: any) { - if (!registered) { + if (!globalState.registered) { throw new Error( 'Mocha hooks were not registered before expectSnapshot was used. Call `registerMochaHooksForSnapshots` in your top-level describe().' ); } - if (!testContext) { - throw new Error('A current Mocha context is needed to match snapshots'); + if (!globalState.currentTest) { + throw new Error('expectSnapshot can only be called inside of an it()'); + } + + const [, fileOfTest] = callsites().map((site) => site.getFileName()); + + if (!fileOfTest) { + throw new Error("Couldn't infer a filename for the current test"); + } + + let snapshot = globalState.snapshots.find(({ file }) => file === fileOfTest); + + if (!snapshot) { + snapshot = { + file: fileOfTest, + tests: [], + snapshotState: getSnapshotState(fileOfTest, globalState.updateSnapshot), + }; + globalState.snapshots.unshift(snapshot!); + } + + if (!snapshot) { + throw new Error('Snapshot is undefined'); + } + + if (!snapshot.tests.includes(globalState.currentTest)) { + snapshot.tests.push(globalState.currentTest); } + const context: SnapshotContext = { + snapshotState: snapshot.snapshotState, + currentTestName: globalState.currentTest.fullTitle(), + }; + return { - toMatch: expectToMatchSnapshot.bind(null, testContext.snapshotContext, received), + toMatch: expectToMatchSnapshot.bind(null, context, received), // use bind to support optional 3rd argument (actual) - toMatchInline: expectToMatchInlineSnapshot.bind(null, testContext.snapshotContext, received), + toMatchInline: expectToMatchInlineSnapshot.bind(null, context, received), }; } diff --git a/x-pack/test/api_integration/apis/uptime/rest/__snapshots__/monitor_states_real_data.snap b/x-pack/test/api_integration/apis/uptime/rest/__snapshots__/monitor_states_real_data.snap index aa21c54da6353..f8c068005b862 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/__snapshots__/monitor_states_real_data.snap +++ b/x-pack/test/api_integration/apis/uptime/rest/__snapshots__/monitor_states_real_data.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`monitor states endpoint will fetch monitor state data for the given down filters 1`] = ` +exports[`apis uptime uptime REST endpoints with real-world data monitor states endpoint will fetch monitor state data for the given down filters 1`] = ` Object { "nextPagePagination": "{\\"cursorDirection\\":\\"AFTER\\",\\"sortOrder\\":\\"ASC\\",\\"cursorKey\\":{\\"monitor_id\\":\\"0020-down\\"}}", "prevPagePagination": null, diff --git a/x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap b/x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap index 43a6ed6f0760f..fe7f434aad2e1 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap +++ b/x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Throughput when data is loaded returns the service throughput has the correct throughput 1`] = ` +exports[`APM specs (basic) Services Throughput when data is loaded returns the service throughput has the correct throughput 1`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap b/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap index a4104d4083a60..56e82d752dccd 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap +++ b/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Top traces when data is loaded returns the correct buckets 1`] = ` +exports[`APM specs (basic) Traces Top traces when data is loaded returns the correct buckets 1`] = ` Array [ Object { "averageResponseTime": 1733, diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap b/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap index 0b83a910bc1a8..25aa68d2a86b1 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap +++ b/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Breakdown when data is loaded returns the transaction breakdown for a service 1`] = ` +exports[`APM specs (basic) Transactions Breakdown when data is loaded returns the transaction breakdown for a service 1`] = ` Object { "timeseries": Array [ Object { @@ -1019,7 +1019,7 @@ Object { } `; -exports[`Breakdown when data is loaded returns the transaction breakdown for a transaction group 9`] = ` +exports[`APM specs (basic) Transactions Breakdown when data is loaded returns the transaction breakdown for a transaction group 9`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap b/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap index 997c4da24f485..3b67a86ba84e8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap +++ b/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Error rate when data is loaded returns the transaction error rate has the correct error rate 1`] = ` +exports[`APM specs (basic) Transactions Error rate when data is loaded returns the transaction error rate has the correct error rate 1`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap b/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap index 417cca8fcaf74..473305f3e39af 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap +++ b/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Top transaction groups when data is loaded returns the correct buckets (when ignoring samples) 1`] = ` +exports[`APM specs (basic) Transactions Top transaction groups when data is loaded returns the correct buckets (when ignoring samples) 1`] = ` Array [ Object { "averageResponseTime": 2722.75, diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap index 4bf242d8f9b6d..c8681866169a5 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap +++ b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UX page load dist when there is data returns page load distribution 1`] = ` +exports[`APM specs (trial) CSM UX page load dist when there is data returns page load distribution 1`] = ` Object { "maxDuration": 54.46, "minDuration": 0, @@ -456,7 +456,7 @@ Object { } `; -exports[`UX page load dist when there is data returns page load distribution with breakdown 1`] = ` +exports[`APM specs (trial) CSM UX page load dist when there is data returns page load distribution with breakdown 1`] = ` Array [ Object { "data": Array [ @@ -819,6 +819,6 @@ Array [ ] `; -exports[`UX page load dist when there is no data returns empty list 1`] = `Object {}`; +exports[`APM specs (trial) CSM UX page load dist when there is no data returns empty list 1`] = `Object {}`; -exports[`UX page load dist when there is no data returns empty list with breakdowns 1`] = `Object {}`; +exports[`APM specs (trial) CSM UX page load dist when there is no data returns empty list with breakdowns 1`] = `Object {}`; diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap index 38b009fc73d34..76e5180ba2141 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap +++ b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CSM page views when there is data returns page views 1`] = ` +exports[`APM specs (trial) CSM CSM page views when there is data returns page views 1`] = ` Object { "items": Array [ Object { @@ -128,7 +128,7 @@ Object { } `; -exports[`CSM page views when there is data returns page views with breakdown 1`] = ` +exports[`APM specs (trial) CSM CSM page views when there is data returns page views with breakdown 1`] = ` Object { "items": Array [ Object { @@ -265,14 +265,14 @@ Object { } `; -exports[`CSM page views when there is no data returns empty list 1`] = ` +exports[`APM specs (trial) CSM CSM page views when there is no data returns empty list 1`] = ` Object { "items": Array [], "topItems": Array [], } `; -exports[`CSM page views when there is no data returns empty list with breakdowns 1`] = ` +exports[`APM specs (trial) CSM CSM page views when there is no data returns empty list with breakdowns 1`] = ` Object { "items": Array [], "topItems": Array [], diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap b/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap index e4f87e3e49ffe..7639822eaa6f9 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap +++ b/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Service Maps with a trial license /api/apm/service-map when there is data returns service map elements filtering by environment not defined 1`] = ` +exports[`APM specs (trial) Service Maps Service Maps with a trial license /api/apm/service-map when there is data returns service map elements filtering by environment not defined 1`] = ` Object { "elements": Array [ Object { @@ -514,7 +514,7 @@ Object { } `; -exports[`Service Maps with a trial license /api/apm/service-map when there is data returns the correct data 3`] = ` +exports[`APM specs (trial) Service Maps Service Maps with a trial license /api/apm/service-map when there is data returns the correct data 3`] = ` Array [ Object { "data": Object { @@ -1741,7 +1741,7 @@ Array [ ] `; -exports[`Service Maps with a trial license when there is data with anomalies with the default apm user returns the correct anomaly stats 3`] = ` +exports[`APM specs (trial) Service Maps Service Maps with a trial license when there is data with anomalies with the default apm user returns the correct anomaly stats 3`] = ` Object { "elements": Array [ Object { diff --git a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap index 99d4026dcdb2c..9475670387a08 100644 --- a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap +++ b/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Latency when data is loaded and fetching transaction charts with uiFilters when not defined environments seleted should return the correct anomaly boundaries 1`] = ` +exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters when not defined environments seleted should return the correct anomaly boundaries 1`] = ` Array [ Object { "x": 1607436000000, @@ -15,7 +15,7 @@ Array [ ] `; -exports[`Latency when data is loaded and fetching transaction charts with uiFilters with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` +exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` Array [ Object { "x": 1607436000000, @@ -30,7 +30,7 @@ Array [ ] `; -exports[`Latency when data is loaded and fetching transaction charts with uiFilters with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` +exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` Array [ Object { "x": 1607436000000, diff --git a/yarn.lock b/yarn.lock index d1d39c0a37d24..cc32349b10860 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9027,6 +9027,11 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== +callsites@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + camel-case@3.0.x, camel-case@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" From b5d2d89c14a7b6f19f9e0ae6813a4b023b31dbb7 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Thu, 21 Jan 2021 17:21:28 +0000 Subject: [PATCH 62/83] [ML] Fixing syncing of deleted job in the * space (#88968) * [ML] Fixing syncing of deleted job in the * space * small refactor --- x-pack/plugins/ml/server/saved_objects/service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/server/saved_objects/service.ts b/x-pack/plugins/ml/server/saved_objects/service.ts index e4833b42b6a2b..9de82269429a0 100644 --- a/x-pack/plugins/ml/server/saved_objects/service.ts +++ b/x-pack/plugins/ml/server/saved_objects/service.ts @@ -131,8 +131,10 @@ export function jobSavedObjectServiceFactory( type: jobType, }); + // * space cannot be used in a delete call, so use undefined which + // is the same as specifying the default space await internalSavedObjectsClient.delete(ML_SAVED_OBJECT_TYPE, id, { - namespace, + namespace: namespace === '*' ? undefined : namespace, force: true, }); } From 7727ab74d2979c9c4c13a56d5809ca288020f2a9 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 21 Jan 2021 18:45:13 +0100 Subject: [PATCH 63/83] [Docs] Clean up state management examples (#88980) --- examples/state_containers_examples/README.md | 2 +- .../state_containers_examples/common/index.ts | 10 - .../state_containers_examples/kibana.json | 4 +- .../public/common/example_page.tsx | 62 +++++ .../public/plugin.ts | 106 ++++---- .../public/state_sync.png | Bin 0 -> 14406 bytes .../public/todo/app.tsx | 33 +-- .../public/todo/todo.tsx | 255 +++++++----------- .../{components => }/app.tsx | 148 +++++----- .../public/with_data_services/application.tsx | 14 +- .../state_containers_examples/server/index.ts | 17 -- .../server/plugin.ts | 45 ---- .../server/routes/index.ts | 25 -- .../state_containers_examples/server/types.ts | 12 - src/plugins/kibana_react/kibana.json | 3 +- src/plugins/kibana_react/public/index.ts | 1 - .../public/use_url_tracker/index.ts | 9 - .../use_url_tracker/use_url_tracker.test.tsx | 59 ---- .../use_url_tracker/use_url_tracker.tsx | 41 --- 19 files changed, 302 insertions(+), 544 deletions(-) delete mode 100644 examples/state_containers_examples/common/index.ts create mode 100644 examples/state_containers_examples/public/common/example_page.tsx create mode 100644 examples/state_containers_examples/public/state_sync.png rename examples/state_containers_examples/public/with_data_services/{components => }/app.tsx (58%) delete mode 100644 examples/state_containers_examples/server/index.ts delete mode 100644 examples/state_containers_examples/server/plugin.ts delete mode 100644 examples/state_containers_examples/server/routes/index.ts delete mode 100644 examples/state_containers_examples/server/types.ts delete mode 100644 src/plugins/kibana_react/public/use_url_tracker/index.ts delete mode 100644 src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.test.tsx delete mode 100644 src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.tsx diff --git a/examples/state_containers_examples/README.md b/examples/state_containers_examples/README.md index c4c6642789bd9..015959a2f7819 100644 --- a/examples/state_containers_examples/README.md +++ b/examples/state_containers_examples/README.md @@ -2,7 +2,7 @@ This example app shows how to: - Use state containers to manage your application state - - Integrate with browser history and hash history routing + - Integrate with browser history or hash history routing - Sync your state container with the URL To run this example, use the command `yarn start --run-examples`. diff --git a/examples/state_containers_examples/common/index.ts b/examples/state_containers_examples/common/index.ts deleted file mode 100644 index 0d0bc48fca450..0000000000000 --- a/examples/state_containers_examples/common/index.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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -export const PLUGIN_ID = 'stateContainersExampleWithDataServices'; -export const PLUGIN_NAME = 'State containers example - with data services'; diff --git a/examples/state_containers_examples/kibana.json b/examples/state_containers_examples/kibana.json index 58346af8f1d19..0f0a3a805ecb5 100644 --- a/examples/state_containers_examples/kibana.json +++ b/examples/state_containers_examples/kibana.json @@ -2,9 +2,9 @@ "id": "stateContainersExamples", "version": "0.0.1", "kibanaVersion": "kibana", - "server": true, + "server": false, "ui": true, "requiredPlugins": ["navigation", "data", "developerExamples"], "optionalPlugins": [], - "requiredBundles": ["kibanaUtils", "kibanaReact"] + "requiredBundles": ["kibanaUtils"] } diff --git a/examples/state_containers_examples/public/common/example_page.tsx b/examples/state_containers_examples/public/common/example_page.tsx new file mode 100644 index 0000000000000..203b226158d0e --- /dev/null +++ b/examples/state_containers_examples/public/common/example_page.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React, { PropsWithChildren } from 'react'; +import { EuiPage, EuiPageSideBar, EuiSideNav } from '@elastic/eui'; +import { CoreStart } from '../../../../src/core/public'; + +export interface ExampleLink { + title: string; + appId: string; +} + +interface NavProps { + navigateToApp: CoreStart['application']['navigateToApp']; + exampleLinks: ExampleLink[]; +} + +const SideNav: React.FC = ({ navigateToApp, exampleLinks }: NavProps) => { + const navItems = exampleLinks.map((example) => ({ + id: example.appId, + name: example.title, + onClick: () => navigateToApp(example.appId), + 'data-test-subj': example.appId, + })); + + return ( + + ); +}; + +interface Props { + navigateToApp: CoreStart['application']['navigateToApp']; + exampleLinks: ExampleLink[]; +} + +export const StateContainersExamplesPage: React.FC = ({ + navigateToApp, + children, + exampleLinks, +}: PropsWithChildren) => { + return ( + + + + + {children} + + ); +}; diff --git a/examples/state_containers_examples/public/plugin.ts b/examples/state_containers_examples/public/plugin.ts index 752c0935c5dd0..a775c3d65fd7a 100644 --- a/examples/state_containers_examples/public/plugin.ts +++ b/examples/state_containers_examples/public/plugin.ts @@ -8,8 +8,8 @@ import { AppMountParameters, CoreSetup, Plugin, AppNavLinkStatus } from '../../../src/core/public'; import { AppPluginDependencies } from './with_data_services/types'; -import { PLUGIN_ID, PLUGIN_NAME } from '../common'; import { DeveloperExamplesSetup } from '../../developer_examples/public'; +import image from './state_sync.png'; interface SetupDeps { developerExamples: DeveloperExamplesSetup; @@ -17,97 +17,95 @@ interface SetupDeps { export class StateContainersExamplesPlugin implements Plugin { public setup(core: CoreSetup, { developerExamples }: SetupDeps) { + const examples = { + stateContainersExampleBrowserHistory: { + title: 'Todo App (browser history)', + }, + stateContainersExampleHashHistory: { + title: 'Todo App (hash history)', + }, + stateContainersExampleWithDataServices: { + title: 'Search bar integration', + }, + }; + + const exampleLinks = Object.keys(examples).map((id: string) => ({ + appId: id, + title: examples[id as keyof typeof examples].title, + })); + core.application.register({ id: 'stateContainersExampleBrowserHistory', - title: 'State containers example - browser history routing', + title: examples.stateContainersExampleBrowserHistory.title, navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { const { renderApp, History } = await import('./todo/app'); - return renderApp(params, { - appInstanceId: '1', - appTitle: 'Routing with browser history', - historyType: History.Browser, - }); + const [coreStart] = await core.getStartServices(); + return renderApp( + params, + { + appTitle: examples.stateContainersExampleBrowserHistory.title, + historyType: History.Browser, + }, + { navigateToApp: coreStart.application.navigateToApp, exampleLinks } + ); }, }); core.application.register({ id: 'stateContainersExampleHashHistory', - title: 'State containers example - hash history routing', + title: examples.stateContainersExampleHashHistory.title, navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { const { renderApp, History } = await import('./todo/app'); - return renderApp(params, { - appInstanceId: '2', - appTitle: 'Routing with hash history', - historyType: History.Hash, - }); + const [coreStart] = await core.getStartServices(); + return renderApp( + params, + { + appTitle: examples.stateContainersExampleHashHistory.title, + historyType: History.Hash, + }, + { navigateToApp: coreStart.application.navigateToApp, exampleLinks } + ); }, }); core.application.register({ - id: PLUGIN_ID, - title: PLUGIN_NAME, + id: 'stateContainersExampleWithDataServices', + title: examples.stateContainersExampleWithDataServices.title, navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { - // Load application bundle const { renderApp } = await import('./with_data_services/application'); - // Get start services as specified in kibana.json const [coreStart, depsStart] = await core.getStartServices(); - // Render the application - return renderApp(coreStart, depsStart as AppPluginDependencies, params); + return renderApp(coreStart, depsStart as AppPluginDependencies, params, { exampleLinks }); }, }); developerExamples.register({ - appId: 'stateContainersExampleBrowserHistory', - title: 'State containers using browser history', - description: `An example todo app that uses browser history and state container utilities like createStateContainerReactHelpers, - createStateContainer, createKbnUrlStateStorage, createSessionStorageStateStorage, - syncStates and getStateFromKbnUrl to keep state in sync with the URL. Change some parameters, navigate away and then back, and the - state should be preserved.`, + appId: exampleLinks[0].appId, + title: 'State Management', + description: 'Examples of using state containers and state syncing utils.', + image, links: [ { - label: 'README', + label: 'State containers README', href: - 'https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_containers/README.md', + 'https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_containers', iconType: 'logoGithub', size: 's', target: '_blank', }, - ], - }); - - developerExamples.register({ - appId: 'stateContainersExampleHashHistory', - title: 'State containers using hash history', - description: `An example todo app that uses hash history and state container utilities like createStateContainerReactHelpers, - createStateContainer, createKbnUrlStateStorage, createSessionStorageStateStorage, - syncStates and getStateFromKbnUrl to keep state in sync with the URL. Change some parameters, navigate away and then back, and the - state should be preserved.`, - links: [ { - label: 'README', + label: 'State sync utils README', href: - 'https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_containers/README.md', + 'https://github.com/elastic/kibana/tree/master/src/plugins/kibana_utils/docs/state_sync', iconType: 'logoGithub', size: 's', target: '_blank', }, - ], - }); - - developerExamples.register({ - appId: PLUGIN_ID, - title: 'Sync state from a query bar with the url', - description: `Shows how to use data.syncQueryStateWitUrl in combination with state container utilities from kibana_utils to - show a query bar that stores state in the url and is kept in sync. - `, - links: [ { - label: 'README', - href: - 'https://github.com/elastic/kibana/blob/master/src/plugins/data/public/query/state_sync/README.md', - iconType: 'logoGithub', + label: 'Kibana navigation best practices', + href: 'https://www.elastic.co/guide/en/kibana/master/kibana-navigation.html', + iconType: 'logoKibana', size: 's', target: '_blank', }, diff --git a/examples/state_containers_examples/public/state_sync.png b/examples/state_containers_examples/public/state_sync.png new file mode 100644 index 0000000000000000000000000000000000000000..fc8eb0dc10f6a27a0ac02a87edef3d7ec9f706a6 GIT binary patch literal 14406 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 JBViK!e*n1p$>0D0 literal 0 HcmV?d00001 diff --git a/examples/state_containers_examples/public/todo/app.tsx b/examples/state_containers_examples/public/todo/app.tsx index ff4d65009a367..f43ace6acee22 100644 --- a/examples/state_containers_examples/public/todo/app.tsx +++ b/examples/state_containers_examples/public/todo/app.tsx @@ -6,14 +6,14 @@ * Public License, v 1. */ -import { AppMountParameters } from 'kibana/public'; +import { AppMountParameters, CoreStart } from 'kibana/public'; import ReactDOM from 'react-dom'; import React from 'react'; import { createHashHistory } from 'history'; import { TodoAppPage } from './todo'; +import { StateContainersExamplesPage, ExampleLink } from '../common/example_page'; export interface AppOptions { - appInstanceId: string; appTitle: string; historyType: History; } @@ -23,30 +23,21 @@ export enum History { Hash, } +export interface Deps { + navigateToApp: CoreStart['application']['navigateToApp']; + exampleLinks: ExampleLink[]; +} + export const renderApp = ( { appBasePath, element, history: platformHistory }: AppMountParameters, - { appInstanceId, appTitle, historyType }: AppOptions + { appTitle, historyType }: AppOptions, + { navigateToApp, exampleLinks }: Deps ) => { const history = historyType === History.Browser ? platformHistory : createHashHistory(); ReactDOM.render( - { - const stripTrailingSlash = (path: string) => - path.charAt(path.length - 1) === '/' ? path.substr(0, path.length - 1) : path; - const currentAppUrl = stripTrailingSlash(history.createHref(history.location)); - if (historyType === History.Browser) { - // browser history - return currentAppUrl === '' && !history.location.search && !history.location.hash; - } else { - // hashed history - return currentAppUrl === '#' && !history.location.search; - } - }} - />, + + + , element ); diff --git a/examples/state_containers_examples/public/todo/todo.tsx b/examples/state_containers_examples/public/todo/todo.tsx index ba0b7d213f9fd..efe45f15c809b 100644 --- a/examples/state_containers_examples/public/todo/todo.tsx +++ b/examples/state_containers_examples/public/todo/todo.tsx @@ -6,7 +6,7 @@ * Public License, v 1. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { Link, Route, Router, Switch, useLocation } from 'react-router-dom'; import { History } from 'history'; import { @@ -18,21 +18,21 @@ import { EuiPageContentBody, EuiPageHeader, EuiPageHeaderSection, + EuiSpacer, + EuiText, EuiTitle, } from '@elastic/eui'; import { + BaseState, BaseStateContainer, - INullableBaseStateContainer, createKbnUrlStateStorage, - createSessionStorageStateStorage, createStateContainer, - createStateContainerReactHelpers, - PureTransition, - syncStates, getStateFromKbnUrl, - BaseState, + INullableBaseStateContainer, + StateContainer, + syncState, + useContainerSelector, } from '../../../../src/plugins/kibana_utils/public'; -import { useUrlTracker } from '../../../../src/plugins/kibana_react/public'; import { defaultState, pureTransitions, @@ -40,42 +40,24 @@ import { TodoState, } from '../../../../src/plugins/kibana_utils/demos/state_containers/todomvc'; -interface GlobalState { - text: string; -} -interface GlobalStateAction { - setText: PureTransition; -} -const defaultGlobalState: GlobalState = { text: '' }; -const globalStateContainer = createStateContainer( - defaultGlobalState, - { - setText: (state) => (text) => ({ ...state, text }), - } -); - -const GlobalStateHelpers = createStateContainerReactHelpers(); - -const container = createStateContainer(defaultState, pureTransitions); -const { Provider, connect, useTransitions, useState } = createStateContainerReactHelpers< - typeof container ->(); - interface TodoAppProps { filter: 'completed' | 'not-completed' | null; + stateContainer: StateContainer; } -const TodoApp: React.FC = ({ filter }) => { - const { setText } = GlobalStateHelpers.useTransitions(); - const { text } = GlobalStateHelpers.useState(); - const { edit: editTodo, delete: deleteTodo, add: addTodo } = useTransitions(); - const todos = useState().todos; - const filteredTodos = todos.filter((todo) => { - if (!filter) return true; - if (filter === 'completed') return todo.completed; - if (filter === 'not-completed') return !todo.completed; - return true; - }); +const TodoApp: React.FC = ({ filter, stateContainer }) => { + const { edit: editTodo, delete: deleteTodo, add: addTodo } = stateContainer.transitions; + const todos = useContainerSelector(stateContainer, (state) => state.todos); + const filteredTodos = useMemo( + () => + todos.filter((todo) => { + if (!filter) return true; + if (filter === 'completed') return todo.completed; + if (filter === 'not-completed') return !todo.completed; + return true; + }), + [todos, filter] + ); const location = useLocation(); return ( <> @@ -144,158 +126,115 @@ const TodoApp: React.FC = ({ filter }) => { > -

- - setText(e.target.value)} /> -
); }; -const TodoAppConnected = GlobalStateHelpers.connect(() => ({}))( - connect(() => ({}))(TodoApp) -); - export const TodoAppPage: React.FC<{ history: History; - appInstanceId: string; appTitle: string; appBasePath: string; - isInitialRoute: () => boolean; }> = (props) => { const initialAppUrl = React.useRef(window.location.href); - const [useHashedUrl, setUseHashedUrl] = React.useState(false); + const stateContainer = React.useMemo( + () => createStateContainer(defaultState, pureTransitions), + [] + ); - /** - * Replicates what src/legacy/ui/public/chrome/api/nav.ts did - * Persists the url in sessionStorage and tries to restore it on "componentDidMount" - */ - useUrlTracker(`lastUrlTracker:${props.appInstanceId}`, props.history, (urlToRestore) => { - // shouldRestoreUrl: - // App decides if it should restore url or not - // In this specific case, restore only if navigated to initial route - if (props.isInitialRoute()) { - // navigated to the base path, so should restore the url - return true; - } else { - // navigated to specific route, so should not restore the url - return false; - } - }); + // Most of kibana apps persist state in the URL in two ways: + // * Rison encoded. + // * Hashed URL: In the URL only the hash from the state is stored. The state itself is stored in + // the sessionStorage. See `state:storeInSessionStorage` advanced option for more context. + // This example shows how to use both of them + const [useHashedUrl, setUseHashedUrl] = React.useState(false); useEffect(() => { - // have to sync with history passed to react-router - // history v5 will be singleton and this will not be needed + // storage to sync our app state with + // in this case we want to sync state with query params in the URL serialised in rison format + // similar like Discover or Dashboard apps do const kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: useHashedUrl, history: props.history, }); - const sessionStorageStateStorage = createSessionStorageStateStorage(); - - /** - * Restoring global state: - * State restoration similar to what GlobalState in legacy world did - * It restores state both from url and from session storage - */ - const globalStateKey = `_g`; - const globalStateFromInitialUrl = getStateFromKbnUrl( - globalStateKey, - initialAppUrl.current - ); - const globalStateFromCurrentUrl = kbnUrlStateStorage.get(globalStateKey); - const globalStateFromSessionStorage = sessionStorageStateStorage.get( - globalStateKey - ); + // key to store state in the storage. In this case in the key of the query param in the URL + const appStateKey = `_todo`; - const initialGlobalState: GlobalState = { - ...defaultGlobalState, - ...globalStateFromCurrentUrl, - ...globalStateFromSessionStorage, - ...globalStateFromInitialUrl, - }; - globalStateContainer.set(initialGlobalState); - kbnUrlStateStorage.set(globalStateKey, initialGlobalState, { replace: true }); - sessionStorageStateStorage.set(globalStateKey, initialGlobalState); - - /** - * Restoring app local state: - * State restoration similar to what AppState in legacy world did - * It restores state both from url - */ - const appStateKey = `_todo-${props.appInstanceId}`; + // take care of initial state. Make sure state in memory is the same as in the URL before starting any syncing const initialAppState: TodoState = getStateFromKbnUrl(appStateKey, initialAppUrl.current) || kbnUrlStateStorage.get(appStateKey) || defaultState; - container.set(initialAppState); + stateContainer.set(initialAppState); kbnUrlStateStorage.set(appStateKey, initialAppState, { replace: true }); - // start syncing only when made sure, that state in synced - const { stop, start } = syncStates([ - { - stateContainer: withDefaultState(container, defaultState), - storageKey: appStateKey, - stateStorage: kbnUrlStateStorage, - }, - { - stateContainer: withDefaultState(globalStateContainer, defaultGlobalState), - storageKey: globalStateKey, - stateStorage: kbnUrlStateStorage, - }, - { - stateContainer: withDefaultState(globalStateContainer, defaultGlobalState), - storageKey: globalStateKey, - stateStorage: sessionStorageStateStorage, - }, - ]); + // start syncing state between state container and the URL + const { stop, start } = syncState({ + stateContainer: withDefaultState(stateContainer, defaultState), + storageKey: appStateKey, + stateStorage: kbnUrlStateStorage, + }); start(); return () => { stop(); - - // reset state containers - container.set(defaultState); - globalStateContainer.set(defaultGlobalState); }; - }, [props.appInstanceId, props.history, useHashedUrl]); + }, [stateContainer, props.history, useHashedUrl]); return ( - - - - - - -

- State sync example. Instance: ${props.appInstanceId}. {props.appTitle} -

-
- setUseHashedUrl(!useHashedUrl)}> - {useHashedUrl ? 'Use Expanded State' : 'Use Hashed State'} - -
-
- - - - - - - - - - - - - - - -
-
-
+ + + + +

{props.appTitle}

+
+ + +

+ This is a simple TODO app that uses state containers and state syncing utils. It + stores state in the URL similar like Discover or Dashboard apps do.
+ Play with the app and see how the state is persisted in the URL. +
Undo/Redo with browser history also works. +

+
+
+
+ + + + + + + + + + + + + + + +

Most of kibana apps persist state in the URL in two ways:

+
    +
  1. Expanded state in rison format
  2. +
  3. + Just a state hash.
    + In the URL only the hash from the state is stored. The state itself is stored in + the sessionStorage. See `state:storeInSessionStorage` advanced option for more + context. +
  4. +
+

You can switch between these two mods:

+
+ + setUseHashedUrl(!useHashedUrl)}> + {useHashedUrl ? 'Use Expanded State' : 'Use Hashed State'} + +
+
+
); }; diff --git a/examples/state_containers_examples/public/with_data_services/components/app.tsx b/examples/state_containers_examples/public/with_data_services/app.tsx similarity index 58% rename from examples/state_containers_examples/public/with_data_services/components/app.tsx rename to examples/state_containers_examples/public/with_data_services/app.tsx index b526032a5becb..fc84e1e952aaa 100644 --- a/examples/state_containers_examples/public/with_data_services/components/app.tsx +++ b/examples/state_containers_examples/public/with_data_services/app.tsx @@ -6,50 +6,47 @@ * Public License, v 1. */ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { History } from 'history'; -import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { Router } from 'react-router-dom'; import { EuiFieldText, - EuiPage, EuiPageBody, EuiPageContent, EuiPageHeader, + EuiText, EuiTitle, } from '@elastic/eui'; +import { CoreStart } from 'kibana/public'; +import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; -import { CoreStart } from '../../../../../src/core/public'; -import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public'; import { connectToQueryState, - syncQueryStateWithUrl, DataPublicPluginStart, - IIndexPattern, - QueryState, - Filter, esFilters, + Filter, + IIndexPattern, Query, -} from '../../../../../src/plugins/data/public'; + QueryState, + syncQueryStateWithUrl, +} from '../../../../src/plugins/data/public'; import { - BaseState, BaseStateContainer, createStateContainer, - createStateContainerReactHelpers, IKbnUrlStateStorage, - ReduxLikeStateContainer, syncState, -} from '../../../../../src/plugins/kibana_utils/public'; -import { PLUGIN_ID, PLUGIN_NAME } from '../../../common'; + useContainerState, +} from '../../../../src/plugins/kibana_utils/public'; +import { ExampleLink, StateContainersExamplesPage } from '../common/example_page'; interface StateDemoAppDeps { - notifications: CoreStart['notifications']; - http: CoreStart['http']; + navigateToApp: CoreStart['application']['navigateToApp']; navigation: NavigationPublicPluginStart; data: DataPublicPluginStart; history: History; kbnUrlStateStorage: IKbnUrlStateStorage; + exampleLinks: ExampleLink[]; } interface AppState { @@ -61,85 +58,74 @@ const defaultAppState: AppState = { name: '', filters: [], }; -const { - Provider: AppStateContainerProvider, - useState: useAppState, - useContainer: useAppStateContainer, -} = createStateContainerReactHelpers>(); -const App = ({ navigation, data, history, kbnUrlStateStorage }: StateDemoAppDeps) => { - const appStateContainer = useAppStateContainer(); - const appState = useAppState(); +export const App = ({ + navigation, + data, + history, + kbnUrlStateStorage, + exampleLinks, + navigateToApp, +}: StateDemoAppDeps) => { + const appStateContainer = useMemo(() => createStateContainer(defaultAppState), []); + const appState = useContainerState(appStateContainer); useGlobalStateSyncing(data.query, kbnUrlStateStorage); useAppStateSyncing(appStateContainer, data.query, kbnUrlStateStorage); const indexPattern = useIndexPattern(data); if (!indexPattern) - return
No index pattern found. Please create an index patter before loading...
; + return ( +
+ No index pattern found. Please create an index pattern before trying this example... +
+ ); - // Render the application DOM. // Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract. return ( - - + + <> - - - - - -

- -

-
-
- - appStateContainer.set({ ...appState, name: e.target.value })} - aria-label="My name" - /> - -
-
+ + + +

Integration with search bar

+
+
+ +

+ This examples shows how you can use state containers, state syncing utils and + helpers from data plugin to sync your app state and search bar state with the URL. +

+
+ + + + +

+ In addition to state from query bar also sync your arbitrary application state: +

+
+ appStateContainer.set({ ...appState, name: e.target.value })} + aria-label="My name" + /> +
+
-
-
- ); -}; - -export const StateDemoApp = (props: StateDemoAppDeps) => { - const appStateContainer = useCreateStateContainer(defaultAppState); - - return ( - - - + + ); }; -function useCreateStateContainer( - defaultState: State -): ReduxLikeStateContainer { - const stateContainerRef = useRef | null>(null); - if (!stateContainerRef.current) { - stateContainerRef.current = createStateContainer(defaultState); - } - return stateContainerRef.current; -} - function useIndexPattern(data: DataPublicPluginStart) { const [indexPattern, setIndexPattern] = useState(); useEffect(() => { diff --git a/examples/state_containers_examples/public/with_data_services/application.tsx b/examples/state_containers_examples/public/with_data_services/application.tsx index d50c203a2a079..4235446dd06e0 100644 --- a/examples/state_containers_examples/public/with_data_services/application.tsx +++ b/examples/state_containers_examples/public/with_data_services/application.tsx @@ -10,24 +10,26 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { AppMountParameters, CoreStart } from '../../../../src/core/public'; import { AppPluginDependencies } from './types'; -import { StateDemoApp } from './components/app'; +import { App } from './app'; import { createKbnUrlStateStorage } from '../../../../src/plugins/kibana_utils/public/'; +import { ExampleLink } from '../common/example_page'; export const renderApp = ( - { notifications, http }: CoreStart, + { notifications, application }: CoreStart, { navigation, data }: AppPluginDependencies, - { element, history }: AppMountParameters + { element, history }: AppMountParameters, + { exampleLinks }: { exampleLinks: ExampleLink[] } ) => { const kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, history }); ReactDOM.render( - , element ); diff --git a/examples/state_containers_examples/server/index.ts b/examples/state_containers_examples/server/index.ts deleted file mode 100644 index 6ae5d24066711..0000000000000 --- a/examples/state_containers_examples/server/index.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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { PluginInitializerContext } from '../../../src/core/server'; -import { StateDemoServerPlugin } from './plugin'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new StateDemoServerPlugin(initializerContext); -} - -export { StateDemoServerPlugin as Plugin }; -export * from '../common'; diff --git a/examples/state_containers_examples/server/plugin.ts b/examples/state_containers_examples/server/plugin.ts deleted file mode 100644 index 04ab4d7a0fede..0000000000000 --- a/examples/state_containers_examples/server/plugin.ts +++ /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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { - PluginInitializerContext, - CoreSetup, - CoreStart, - Plugin, - Logger, -} from '../../../src/core/server'; - -import { StateDemoPluginSetup, StateDemoPluginStart } from './types'; -import { defineRoutes } from './routes'; - -export class StateDemoServerPlugin implements Plugin { - private readonly logger: Logger; - - constructor(initializerContext: PluginInitializerContext) { - this.logger = initializerContext.logger.get(); - } - - public setup(core: CoreSetup) { - this.logger.debug('State_demo: Ssetup'); - const router = core.http.createRouter(); - - // Register server side APIs - defineRoutes(router); - - return {}; - } - - public start(core: CoreStart) { - this.logger.debug('State_demo: Started'); - return {}; - } - - public stop() {} -} - -export { StateDemoServerPlugin as Plugin }; diff --git a/examples/state_containers_examples/server/routes/index.ts b/examples/state_containers_examples/server/routes/index.ts deleted file mode 100644 index f7c7a6abe8808..0000000000000 --- a/examples/state_containers_examples/server/routes/index.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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { IRouter } from '../../../../src/core/server'; - -export function defineRoutes(router: IRouter) { - router.get( - { - path: '/api/state_demo/example', - validate: false, - }, - async (context, request, response) => { - return response.ok({ - body: { - time: new Date().toISOString(), - }, - }); - } - ); -} diff --git a/examples/state_containers_examples/server/types.ts b/examples/state_containers_examples/server/types.ts deleted file mode 100644 index 86dc8d556e4c1..0000000000000 --- a/examples/state_containers_examples/server/types.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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface StateDemoPluginSetup {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface StateDemoPluginStart {} diff --git a/src/plugins/kibana_react/kibana.json b/src/plugins/kibana_react/kibana.json index c05490c349917..f2f0da53e6280 100644 --- a/src/plugins/kibana_react/kibana.json +++ b/src/plugins/kibana_react/kibana.json @@ -2,6 +2,5 @@ "id": "kibanaReact", "version": "kibana", "ui": true, - "server": false, - "requiredBundles": ["kibanaUtils"] + "server": false } diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 4ec96f1db8199..c99da5e9b36b8 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -21,7 +21,6 @@ export { ValidatedDualRange, Value } from './validated_range'; export * from './notifications'; export { Markdown, MarkdownSimple } from './markdown'; export { reactToUiComponent, uiToReactComponent } from './adapters'; -export { useUrlTracker } from './use_url_tracker'; export { toMountPoint, MountPointPortal } from './util'; export { RedirectAppLinks } from './app_links'; diff --git a/src/plugins/kibana_react/public/use_url_tracker/index.ts b/src/plugins/kibana_react/public/use_url_tracker/index.ts deleted file mode 100644 index 7ba21ddafaef2..0000000000000 --- a/src/plugins/kibana_react/public/use_url_tracker/index.ts +++ /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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -export { useUrlTracker } from './use_url_tracker'; diff --git a/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.test.tsx b/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.test.tsx deleted file mode 100644 index ed3eca04943a6..0000000000000 --- a/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.test.tsx +++ /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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { renderHook } from '@testing-library/react-hooks'; -import { useUrlTracker } from './use_url_tracker'; -import { StubBrowserStorage } from '@kbn/test/jest'; -import { createMemoryHistory } from 'history'; - -describe('useUrlTracker', () => { - const key = 'key'; - let storage = new StubBrowserStorage(); - let history = createMemoryHistory(); - beforeEach(() => { - storage = new StubBrowserStorage(); - history = createMemoryHistory(); - }); - - it('should track history changes and save them to storage', () => { - expect(storage.getItem(key)).toBeNull(); - const { unmount } = renderHook(() => { - useUrlTracker(key, history, () => false, storage); - }); - expect(storage.getItem(key)).toBe('/'); - history.push('/change'); - expect(storage.getItem(key)).toBe('/change'); - unmount(); - history.push('/other-change'); - expect(storage.getItem(key)).toBe('/change'); - }); - - it('by default should restore initial url', () => { - storage.setItem(key, '/change'); - renderHook(() => { - useUrlTracker(key, history, undefined, storage); - }); - expect(history.location.pathname).toBe('/change'); - }); - - it('should restore initial url if shouldRestoreUrl cb returns true', () => { - storage.setItem(key, '/change'); - renderHook(() => { - useUrlTracker(key, history, () => true, storage); - }); - expect(history.location.pathname).toBe('/change'); - }); - - it('should not restore initial url if shouldRestoreUrl cb returns false', () => { - storage.setItem(key, '/change'); - renderHook(() => { - useUrlTracker(key, history, () => false, storage); - }); - expect(history.location.pathname).toBe('/'); - }); -}); diff --git a/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.tsx b/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.tsx deleted file mode 100644 index 5f3caf03ae447..0000000000000 --- a/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { History } from 'history'; -import { useLayoutEffect } from 'react'; -import { createUrlTracker } from '../../../kibana_utils/public/'; - -/** - * State management url_tracker in react hook form - * - * Replicates what src/legacy/ui/public/chrome/api/nav.ts did - * Persists the url in sessionStorage so it could be restored if navigated back to the app - * - * @param key - key to use in storage - * @param history - history instance to use - * @param shouldRestoreUrl - cb if url should be restored - * @param storage - storage to use. window.sessionStorage is default - */ -export function useUrlTracker( - key: string, - history: History, - shouldRestoreUrl: (urlToRestore: string) => boolean = () => true, - storage: Storage = sessionStorage -) { - useLayoutEffect(() => { - const urlTracker = createUrlTracker(key, storage); - const urlToRestore = urlTracker.getTrackedUrl(); - if (urlToRestore && shouldRestoreUrl(urlToRestore)) { - history.replace(urlToRestore); - } - const stopTrackingUrl = urlTracker.startTrackingUrl(history); - return () => { - stopTrackingUrl(); - }; - }, [key, history]); -} From d1e3ee98e5b23aa73e9de895c7af0dedd82d4921 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Thu, 21 Jan 2021 13:08:25 -0500 Subject: [PATCH 64/83] Stop using usingEphemeralEncryptionKey (#88884) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/monitoring/kibana.json | 1 - .../server/lib/elasticsearch/verify_alerting_security.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/monitoring/kibana.json b/x-pack/plugins/monitoring/kibana.json index 501b84dd8825d..d7784465d4519 100644 --- a/x-pack/plugins/monitoring/kibana.json +++ b/x-pack/plugins/monitoring/kibana.json @@ -19,7 +19,6 @@ "triggersActionsUi", "alerts", "actions", - "encryptedSavedObjects", "encryptedSavedObjects" ], "server": true, diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_alerting_security.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_alerting_security.ts index c8aa730dd4774..aff7c4edb5174 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_alerting_security.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_alerting_security.ts @@ -43,7 +43,7 @@ export class AlertingSecurity { return { isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled), - hasPermanentEncryptionKey: !encryptedSavedObjects?.usingEphemeralEncryptionKey, + hasPermanentEncryptionKey: Boolean(encryptedSavedObjects), }; }; } From 933d1b1471817114a33d9d1bedfb3a5e81adbd1a Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 21 Jan 2021 12:10:59 -0600 Subject: [PATCH 65/83] skip "run cancels expired tasks prior to running new tasks" --- x-pack/plugins/task_manager/server/task_pool.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/task_manager/server/task_pool.test.ts b/x-pack/plugins/task_manager/server/task_pool.test.ts index 9161bbf3c28a5..324e376c32d95 100644 --- a/x-pack/plugins/task_manager/server/task_pool.test.ts +++ b/x-pack/plugins/task_manager/server/task_pool.test.ts @@ -203,7 +203,7 @@ describe('TaskPool', () => { sinon.assert.calledOnce(secondRun); }); - test('run cancels expired tasks prior to running new tasks', async () => { + test.skip('run cancels expired tasks prior to running new tasks', async () => { const logger = loggingSystemMock.create().get(); const pool = new TaskPool({ maxWorkers$: of(2), From eaab783410f62e5b2b7e4b5c1f48da080dd177a1 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 21 Jan 2021 12:41:51 -0600 Subject: [PATCH 66/83] [Workplace Search] Add unit tests for top-level Sources components (#88918) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add full source mocks The overview page recieves heavily annotated source data for display. This extends the existing mocks * Refactor for easier readability Uses optional chaining. Hide whitespace changes for easier reviewing of this commit * Remove conditionals The false case will never be true here because the line above only renders when there is a summary. Around line 109: ``` {!!summary && ``` * Refactor GroupsSummary to variable It was challenging to test the null in the original implementation so I refactored to cloer match the way we do this in other places by making the conditional rendering inline, rather than `null` in a function. * Remove unused const * Add overview test-subj attrs * Add overview unit tests * Add tests for SourceAdded * Move meta to shared mock * Add tests for SourceContent * Add tests for SourceInfoCard * Move redirect logic from component to logic file We had this weird callback passing to trigger a redirect and we are already redirecting in the logic file for other things so I simplified this to have the logic file do the redirecting and not have to pass the callback around, which is hard to test and unnecessary complexity. Also using the KibanaLogic navigateToUrl instead of history.push # Conflicts: # x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts * Add tests for SourceSettings * Add tests for SourceSubNav * I am the typo king 🤴🏼Prove me wrong. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__mocks__/content_sources.mock.ts | 57 ++++++ .../workplace_search/__mocks__/meta.mock.ts | 16 ++ .../components/overview.test.tsx | 130 ++++++++++++ .../content_sources/components/overview.tsx | 102 +++++----- .../components/source_added.test.tsx | 54 +++++ .../components/source_content.test.tsx | 186 ++++++++++++++++++ .../components/source_content.tsx | 2 +- .../components/source_info_card.test.tsx | 33 ++++ .../components/source_settings.test.tsx | 108 ++++++++++ .../components/source_settings.tsx | 9 +- .../components/source_sub_nav.test.tsx | 38 ++++ .../views/content_sources/source_logic.ts | 14 +- .../views/groups/groups.test.tsx | 10 +- 13 files changed, 678 insertions(+), 81 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/meta.mock.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.test.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts index 3cd84d90d9a86..0e0d1fa864033 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts @@ -6,6 +6,7 @@ import { mergeServerAndStaticData } from '../views/content_sources/sources_logic'; import { staticSourceData } from '../views/content_sources/source_data'; +import { groups } from './groups.mock'; export const contentSources = [ { @@ -38,6 +39,62 @@ export const contentSources = [ }, ]; +export const fullContentSources = [ + { + ...contentSources[0], + activities: [ + { + details: ['detail'], + event: 'this is an event', + time: '2021-01-20', + status: 'syncing', + }, + ], + details: [ + { + title: 'My Thing', + description: 'This is a thing.', + }, + ], + summary: [ + { + count: 1, + type: 'summary', + }, + ], + groups, + custom: false, + accessToken: '123token', + urlField: 'myLink', + titleField: 'heading', + licenseSupportsPermissions: true, + serviceTypeSupportsPermissions: true, + indexPermissions: true, + hasPermissions: true, + urlFieldIsLinkable: true, + createdAt: '2021-01-20', + serviceName: 'myService', + }, + { + ...contentSources[1], + activities: [], + details: [], + summary: [], + groups: [], + custom: true, + accessToken: '123token', + urlField: 'url', + titleField: 'title', + licenseSupportsPermissions: false, + serviceTypeSupportsPermissions: false, + indexPermissions: false, + hasPermissions: false, + urlFieldIsLinkable: false, + createdAt: '2021-01-20', + serviceName: 'custom', + }, +]; + export const configuredSources = [ { serviceType: 'gmail', diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/meta.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/meta.mock.ts new file mode 100644 index 0000000000000..e596ea5d7e948 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/meta.mock.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DEFAULT_META } from '../../shared/constants'; + +export const mockMeta = { + ...DEFAULT_META, + page: { + current: 1, + total_results: 50, + total_pages: 5, + }, +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.test.tsx new file mode 100644 index 0000000000000..826e863533074 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.test.tsx @@ -0,0 +1,130 @@ +/* + * 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_useeffect.mock'; + +import { setMockValues } from '../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiEmptyPrompt, EuiPanel, EuiTable } from '@elastic/eui'; + +import { fullContentSources } from '../../../__mocks__/content_sources.mock'; + +import { Loading } from '../../../../shared/loading'; +import { ComponentLoader } from '../../../components/shared/component_loader'; + +import { Overview } from './overview'; + +describe('Overview', () => { + const contentSource = fullContentSources[0]; + const dataLoading = false; + const isOrganization = true; + + const mockValues = { + contentSource, + dataLoading, + isOrganization, + }; + + beforeEach(() => { + setMockValues({ ...mockValues }); + }); + + it('renders', () => { + const wrapper = shallow(); + const documentSummary = wrapper.find('[data-test-subj="DocumentSummary"]').dive(); + + expect(documentSummary).toHaveLength(1); + expect(documentSummary.find('[data-test-subj="DocumentSummaryRow"]')).toHaveLength(1); + }); + + it('returns Loading when loading', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('renders ComponentLoader when loading', () => { + setMockValues({ + ...mockValues, + contentSource: { + ...fullContentSources[1], + summary: null, + }, + }); + + const wrapper = shallow(); + const documentSummary = wrapper.find('[data-test-subj="DocumentSummary"]').dive(); + + expect(documentSummary.find(ComponentLoader)).toHaveLength(1); + }); + + it('handles empty states', () => { + setMockValues({ ...mockValues, contentSource: fullContentSources[1] }); + const wrapper = shallow(); + const documentSummary = wrapper.find('[data-test-subj="DocumentSummary"]').dive(); + const activitySummary = wrapper.find('[data-test-subj="ActivitySummary"]').dive(); + + expect(documentSummary.find(EuiEmptyPrompt)).toHaveLength(1); + expect(activitySummary.find(EuiEmptyPrompt)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="GroupsSummary"]')).toHaveLength(0); + }); + + it('renders activity table', () => { + const wrapper = shallow(); + const activitySummary = wrapper.find('[data-test-subj="ActivitySummary"]').dive(); + + expect(activitySummary.find(EuiTable)).toHaveLength(1); + }); + + it('renders GroupsSummary', () => { + const wrapper = shallow(); + const groupsSummary = wrapper.find('[data-test-subj="GroupsSummary"]').dive(); + + expect(groupsSummary.find('[data-test-subj="SourceGroupLink"]')).toHaveLength(1); + }); + + it('renders DocumentationCallout', () => { + setMockValues({ ...mockValues, contentSource: fullContentSources[1] }); + const wrapper = shallow(); + const documentationCallout = wrapper.find('[data-test-subj="DocumentationCallout"]').dive(); + + expect(documentationCallout.find(EuiPanel)).toHaveLength(1); + }); + + it('renders PermissionsStatus', () => { + setMockValues({ + ...mockValues, + contentSource: { + ...fullContentSources[0], + serviceTypeSupportsPermissions: true, + hasPermissions: false, + }, + }); + + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="PermissionsStatus"]')).toHaveLength(1); + }); + + it('renders DocumentPermissionsDisabled', () => { + setMockValues({ + ...mockValues, + contentSource: { + ...fullContentSources[1], + serviceTypeSupportsPermissions: true, + custom: false, + }, + }); + + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="DocumentPermissionsDisabled"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx index 71d79b4b2a082..a0797305de6ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx @@ -55,7 +55,6 @@ export const Overview: React.FC = () => { const { id, summary, - documentCount, activities, groups, details, @@ -72,24 +71,22 @@ export const Overview: React.FC = () => { const DocumentSummary = () => { let totalDocuments = 0; - const tableContent = - summary && - summary.map((item, index) => { - totalDocuments += item.count; - return ( - item.count > 0 && ( - - {item.type} - {item.count.toLocaleString('en-US')} - - ) - ); - }); + const tableContent = summary?.map((item, index) => { + totalDocuments += item.count; + return ( + item.count > 0 && ( + + {item.type} + {item.count.toLocaleString('en-US')} + + ) + ); + }); const emptyState = ( <> - + No content yet
} iconType="documents" @@ -121,14 +118,10 @@ export const Overview: React.FC = () => { {tableContent} - {summary ? Total documents : 'Documents'} + Total documents - {summary ? ( - {totalDocuments.toLocaleString('en-US')} - ) : ( - parseInt(documentCount, 10).toLocaleString('en-US') - )} + {totalDocuments.toLocaleString('en-US')} @@ -142,7 +135,7 @@ export const Overview: React.FC = () => { const emptyState = ( <> - + There is no recent activity} iconType="clock" @@ -202,31 +195,29 @@ export const Overview: React.FC = () => { ); }; - const GroupsSummary = () => { - return !groups.length ? null : ( - <> - -

Group Access

-
- - - {groups.map((group, index) => ( - - - - {group.name} - - - - ))} - - - ); - }; + const groupsSummary = ( + <> + +

Group Access

+
+ + + {groups.map((group, index) => ( + + + + {group.name} + + + + ))} + + + ); const detailsSummary = ( <> @@ -285,7 +276,7 @@ export const Overview: React.FC = () => {

Document-level permissions

- + @@ -333,7 +324,7 @@ export const Overview: React.FC = () => { ); const permissionsStatus = ( - +
Status @@ -426,20 +417,18 @@ export const Overview: React.FC = () => { - + {!isFederatedSource && ( - + )} - - - + {groups.length > 0 && groupsSummary} {details.length > 0 && {detailsSummary}} {!custom && serviceTypeSupportsPermissions && ( <> @@ -458,7 +447,10 @@ export const Overview: React.FC = () => { {sourceStatus} {credentials} - +

Learn more diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx new file mode 100644 index 0000000000000..d29995484540c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.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_useeffect.mock'; + +import { setMockValues, setMockActions, mockFlashMessageHelpers } from '../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { Redirect, useLocation } from 'react-router-dom'; + +import { SourceAdded } from './source_added'; + +describe('SourceAdded', () => { + const { setErrorMessage } = mockFlashMessageHelpers; + const setAddedSource = jest.fn(); + + beforeEach(() => { + setMockActions({ setAddedSource }); + setMockValues({ isOrganization: true }); + }); + + it('renders', () => { + const search = '?name=foo&serviceType=custom&indexPermissions=false'; + (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); + const wrapper = shallow(); + + expect(wrapper.find(Redirect)).toHaveLength(1); + expect(setAddedSource).toHaveBeenCalled(); + }); + + describe('hasError', () => { + it('passes default error to server', () => { + const search = '?name=foo&hasError=true&serviceType=custom&indexPermissions=false'; + (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); + shallow(); + + expect(setErrorMessage).toHaveBeenCalledWith('foo failed to connect.'); + }); + + it('passes custom error to server', () => { + const search = + '?name=foo&hasError=true&serviceType=custom&indexPermissions=false&errorMessages[]=custom error'; + (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); + shallow(); + + expect(setErrorMessage).toHaveBeenCalledWith('custom error'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx new file mode 100644 index 0000000000000..c445a7aec04f6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.test.tsx @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + EuiTable, + EuiButton, + EuiButtonEmpty, + EuiEmptyPrompt, + EuiFieldSearch, + EuiLink, +} from '@elastic/eui'; + +import { mockMeta } from '../../../__mocks__/meta.mock'; +import { fullContentSources } from '../../../__mocks__/content_sources.mock'; + +import { DEFAULT_META } from '../../../../shared/constants'; +import { ComponentLoader } from '../../../components/shared/component_loader'; +import { Loading } from '../../../../../applications/shared/loading'; +import { TablePaginationBar } from '../../../components/shared/table_pagination_bar'; + +import { SourceContent } from './source_content'; + +describe('SourceContent', () => { + const setActivePage = jest.fn(); + const searchContentSourceDocuments = jest.fn(); + const resetSourceState = jest.fn(); + const setContentFilterValue = jest.fn(); + + const mockValues = { + contentSource: fullContentSources[0], + contentMeta: mockMeta, + contentItems: [ + { + id: '1234', + last_updated: '2021-01-21', + }, + { + id: '1235', + last_updated: '2021-01-20', + }, + ], + contentFilterValue: '', + dataLoading: false, + sectionLoading: false, + isOrganization: true, + }; + + beforeEach(() => { + setMockActions({ + setActivePage, + searchContentSourceDocuments, + resetSourceState, + setContentFilterValue, + }); + setMockValues({ ...mockValues }); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiTable)).toHaveLength(1); + }); + + it('returns Loading when loading', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('returns ComponentLoader when section loading', () => { + setMockValues({ ...mockValues, sectionLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(ComponentLoader)).toHaveLength(1); + }); + + describe('empty states', () => { + beforeEach(() => { + setMockValues({ ...mockValues, contentMeta: DEFAULT_META }); + }); + it('renders', () => { + setMockValues({ ...mockValues, contentMeta: DEFAULT_META }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); + expect(wrapper.find(EuiEmptyPrompt).prop('body')).toBeTruthy(); + expect(wrapper.find(EuiEmptyPrompt).prop('title')).toEqual( +

This source doesn't have any content yet

+ ); + }); + + it('shows custom source docs link', () => { + setMockValues({ + ...mockValues, + contentMeta: DEFAULT_META, + contentSource: { + ...fullContentSources[0], + serviceType: 'google', + }, + }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt).prop('body')).toBeNull(); + }); + + it('shows correct message when filter value set', () => { + setMockValues({ ...mockValues, contentMeta: DEFAULT_META, contentFilterValue: 'Elastic' }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt).prop('title')).toEqual( +

No results for 'Elastic'

+ ); + }); + }); + + it('handles page change', () => { + const wrapper = shallow(); + const tablePager = wrapper.find(TablePaginationBar).first(); + tablePager.prop('onChangePage')(3); + + expect(setActivePage).toHaveBeenCalledWith(4); + }); + + it('clears filter value when reset', () => { + setMockValues({ + ...mockValues, + contentSource: { + ...fullContentSources[0], + isFederatedSource: true, + }, + }); + const wrapper = shallow(); + const button = wrapper.find(EuiButtonEmpty); + button.simulate('click'); + + expect(setContentFilterValue).toHaveBeenCalledWith(''); + }); + + it('sets filter value', () => { + setMockValues({ + ...mockValues, + contentSource: { + ...fullContentSources[0], + isFederatedSource: true, + }, + }); + const wrapper = shallow(); + const input = wrapper.find(EuiFieldSearch); + input.simulate('change', { target: { value: 'Query' } }); + const button = wrapper.find(EuiButton); + button.simulate('click'); + + expect(setContentFilterValue).toHaveBeenCalledWith(''); + }); + + describe('URL field link', () => { + it('does not render link when not linkable', () => { + setMockValues({ + ...mockValues, + contentSource: fullContentSources[1], + }); + const wrapper = shallow(); + const fieldCell = wrapper.find('[data-test-subj="URLFieldCell"]'); + + expect(fieldCell.find(EuiLink)).toHaveLength(0); + }); + + it('renders links when linkable', () => { + const wrapper = shallow(); + const fieldCell = wrapper.find('[data-test-subj="URLFieldCell"]'); + + expect(fieldCell.find(EuiLink)).toHaveLength(2); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx index 8d9636ec38e1f..728d21eb1530f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx @@ -122,7 +122,7 @@ export const SourceContent: React.FC = () => { - + {!urlFieldIsLinkable && ( )} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.test.tsx new file mode 100644 index 0000000000000..0a01fecfc91bb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiBadge, EuiHealth, EuiText, EuiTitle } from '@elastic/eui'; + +import { SourceIcon } from '../../../components/shared/source_icon'; + +import { SourceInfoCard } from './source_info_card'; + +describe('SourceInfoCard', () => { + const props = { + sourceName: 'source', + sourceType: 'custom', + dateCreated: '2021-01-20', + isFederatedSource: true, + }; + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(SourceIcon)).toHaveLength(1); + expect(wrapper.find(EuiBadge)).toHaveLength(1); + expect(wrapper.find(EuiHealth)).toHaveLength(1); + expect(wrapper.find(EuiText)).toHaveLength(3); + expect(wrapper.find(EuiTitle)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx new file mode 100644 index 0000000000000..11e74d8246a46 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiConfirmModal } from '@elastic/eui'; + +import { fullContentSources, sourceConfigData } from '../../../__mocks__/content_sources.mock'; + +import { SourceConfigFields } from '../../../components/shared/source_config_fields'; + +import { SourceSettings } from './source_settings'; + +describe('SourceSettings', () => { + const updateContentSource = jest.fn(); + const removeContentSource = jest.fn(); + const resetSourceState = jest.fn(); + const getSourceConfigData = jest.fn(); + const contentSource = fullContentSources[0]; + const buttonLoading = false; + const isOrganization = true; + + const mockValues = { + contentSource, + buttonLoading, + sourceConfigData, + isOrganization, + }; + + beforeEach(() => { + setMockValues({ ...mockValues }); + setMockActions({ + updateContentSource, + removeContentSource, + resetSourceState, + getSourceConfigData, + }); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find('form')).toHaveLength(1); + }); + + it('handles form submission', () => { + const wrapper = shallow(); + + const TEXT = 'name'; + const input = wrapper.find('[data-test-subj="SourceNameInput"]'); + input.simulate('change', { target: { value: TEXT } }); + + const preventDefault = jest.fn(); + wrapper.find('form').simulate('submit', { preventDefault }); + + expect(preventDefault).toHaveBeenCalled(); + expect(updateContentSource).toHaveBeenCalledWith(fullContentSources[0].id, { name: TEXT }); + }); + + it('handles confirmModal submission', () => { + const wrapper = shallow(); + wrapper.find('[data-test-subj="DeleteSourceButton"]').simulate('click'); + + const modal = wrapper.find(EuiConfirmModal); + modal.prop('onConfirm')!({} as any); + modal.prop('onCancel')!({} as any); + + expect(removeContentSource).toHaveBeenCalled(); + }); + + it('falls back when no configured fields sent', () => { + setMockValues({ ...mockValues, sourceConfigData: {} }); + const wrapper = shallow(); + + expect(wrapper.find('form')).toHaveLength(1); + }); + + it('falls back when no consumerKey field sent', () => { + setMockValues({ ...mockValues, sourceConfigData: { configuredFields: { clientId: '123' } } }); + const wrapper = shallow(); + + expect(wrapper.find(SourceConfigFields).prop('consumerKey')).toBeUndefined(); + }); + + it('handles public key use case', () => { + setMockValues({ + ...mockValues, + contentSource: { + ...fullContentSources[0], + serviceType: 'confluence_server', + }, + }); + + const wrapper = shallow(); + + expect(wrapper.find(SourceConfigFields).prop('publicKey')).toEqual( + sourceConfigData.configuredFields.publicKey + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx index 8ca31d184501f..8d3219be9b02a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx @@ -6,10 +6,9 @@ import React, { useEffect, useState, ChangeEvent, FormEvent } from 'react'; -import { History } from 'history'; import { useActions, useValues } from 'kea'; import { isEmpty } from 'lodash'; -import { Link, useHistory } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import { EuiButton, @@ -22,8 +21,6 @@ import { EuiFormRow, } from '@elastic/eui'; -import { SOURCES_PATH, getSourcesPath } from '../../../routes'; - import { ContentSection } from '../../../components/shared/content_section'; import { SourceConfigFields } from '../../../components/shared/source_config_fields'; import { ViewContentHeader } from '../../../components/shared/view_content_header'; @@ -35,7 +32,6 @@ import { staticSourceData } from '../source_data'; import { SourceLogic } from '../source_logic'; export const SourceSettings: React.FC = () => { - const history = useHistory() as History; const { updateContentSource, removeContentSource, @@ -83,8 +79,7 @@ export const SourceSettings: React.FC = () => { * modal here and set the button that was clicked to delete to a loading state. */ setModalVisibility(false); - const onSourceRemoved = () => history.push(getSourcesPath(SOURCES_PATH, isOrganization)); - removeContentSource(id, onSourceRemoved); + removeContentSource(id); }; const confirmModal = ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.test.tsx new file mode 100644 index 0000000000000..a90002e5d553e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockValues } from '../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { CUSTOM_SERVICE_TYPE } from '../../../constants'; +import { SourceSubNav } from './source_sub_nav'; + +import { SideNavLink } from '../../../../shared/layout'; + +describe('SourceSubNav', () => { + it('renders empty when no group id present', () => { + setMockValues({ contentSource: {} }); + const wrapper = shallow(); + + expect(wrapper.find(SideNavLink)).toHaveLength(0); + }); + + it('renders nav items', () => { + setMockValues({ contentSource: { id: '1' } }); + const wrapper = shallow(); + + expect(wrapper.find(SideNavLink)).toHaveLength(3); + }); + + it('renders custom source nav items', () => { + setMockValues({ contentSource: { id: '1', serviceType: CUSTOM_SERVICE_TYPE } }); + const wrapper = shallow(); + + expect(wrapper.find(SideNavLink)).toHaveLength(5); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index 9a68d2234e3ad..fe958db9d0232 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -20,7 +20,7 @@ import { import { DEFAULT_META } from '../../../shared/constants'; import { AppLogic } from '../../app_logic'; -import { NOT_FOUND_PATH } from '../../routes'; +import { NOT_FOUND_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; import { ContentSourceFullData, Meta, DocumentSummaryItem, SourceContentItem } from '../../types'; export interface SourceActions { @@ -38,10 +38,7 @@ export interface SourceActions { source: { name: string } ): { sourceId: string; source: { name: string } }; resetSourceState(): void; - removeContentSource( - sourceId: string, - successCallback: () => void - ): { sourceId: string; successCallback(): void }; + removeContentSource(sourceId: string): { sourceId: string }; initializeSource(sourceId: string, history: object): { sourceId: string; history: object }; getSourceConfigData(serviceType: string): { serviceType: string }; setButtonNotLoading(): void; @@ -95,9 +92,8 @@ export const SourceLogic = kea>({ initializeFederatedSummary: (sourceId: string) => ({ sourceId }), searchContentSourceDocuments: (sourceId: string) => ({ sourceId }), updateContentSource: (sourceId: string, source: { name: string }) => ({ sourceId, source }), - removeContentSource: (sourceId: string, successCallback: () => void) => ({ + removeContentSource: (sourceId: string) => ({ sourceId, - successCallback, }), getSourceConfigData: (serviceType: string) => ({ serviceType }), resetSourceState: () => true, @@ -245,7 +241,7 @@ export const SourceLogic = kea>({ flashAPIErrors(e); } }, - removeContentSource: async ({ sourceId, successCallback }) => { + removeContentSource: async ({ sourceId }) => { clearFlashMessages(); const { isOrganization } = AppLogic.values; const route = isOrganization @@ -263,7 +259,7 @@ export const SourceLogic = kea>({ } ) ); - successCallback(); + KibanaLogic.values.navigateToUrl(getSourcesPath(SOURCES_PATH, isOrganization)); } catch (e) { flashAPIErrors(e); } finally { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx index 5412924438ca6..7c746f75ffc94 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx @@ -7,6 +7,7 @@ import '../../../__mocks__/shallow_useeffect.mock'; import { setMockActions, setMockValues } from '../../../__mocks__'; import { groups } from '../../__mocks__/groups.mock'; +import { mockMeta } from '../../__mocks__/meta.mock'; import React from 'react'; import { shallow } from 'enzyme'; @@ -33,15 +34,6 @@ const resetGroups = jest.fn(); const setFilterValue = jest.fn(); const setActivePage = jest.fn(); -const mockMeta = { - ...DEFAULT_META, - page: { - current: 1, - total_results: 50, - total_pages: 5, - }, -}; - const mockSuccessMessage = { type: 'success', message: 'group added', From 88be8a71483ecc4ea697d7944479bde60de36c53 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Thu, 21 Jan 2021 13:42:39 -0500 Subject: [PATCH 67/83] [Fleet] Remove support for shared_id during enrollment (#88897) --- .../plugins/fleet/common/openapi/bundled.json | 1491 ++++++++--------- .../plugins/fleet/common/openapi/bundled.yaml | 1024 ++++++----- .../openapi/components/schemas/agent.yaml | 1 + .../common/openapi/paths/agents@enroll.yaml | 1 + .../fleet/common/types/models/agent.ts | 1 - .../fleet/common/types/rest_spec/agent.ts | 1 - .../fleet/dev_docs/api/agents_enroll.md | 10 - .../fleet/server/routes/agent/handlers.ts | 3 +- .../fleet/server/saved_objects/index.ts | 4 +- .../saved_objects/migrations/to_v7_12_0.ts | 16 + .../fleet/server/services/agents/enroll.ts | 52 +- .../fleet/server/types/rest_spec/agent.ts | 1 + .../apis/agents/enroll.ts | 22 - .../fleet_api_integration/apis/agents/list.ts | 4 +- .../es_archives/fleet/agents/data.json | 6 +- 15 files changed, 1240 insertions(+), 1397 deletions(-) create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index e9b11a2f5ac83..55c32802c3334 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -32,7 +32,7 @@ "items": { "type": "array", "items": { - "$ref": "#/components/schemas/agent_policy" + "$ref": "#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item" } }, "total": { @@ -59,13 +59,31 @@ "operationId": "agent-policy-list", "parameters": [ { - "$ref": "#/components/parameters/page_size" + "name": "perPage", + "in": "query", + "description": "The number of items to return", + "required": false, + "schema": { + "type": "integer", + "default": 50 + } }, { - "$ref": "#/components/parameters/page_index" + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 1 + } }, { - "$ref": "#/components/parameters/kuery" + "name": "kuery", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } ], "description": "" @@ -82,7 +100,58 @@ "type": "object", "properties": { "item": { - "$ref": "#/components/schemas/agent_policy" + "allOf": [ + { + "$ref": "#/paths/~1agent_policies/post/requestBody/content/application~1json/schema" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "packagePolicies": { + "oneOf": [ + { + "items": { + "type": "string" + } + }, + { + "items": { + "$ref": "#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item" + } + } + ], + "type": "array" + }, + "updated_on": { + "type": "string", + "format": "date-time" + }, + "updated_by": { + "type": "string" + }, + "revision": { + "type": "number" + }, + "agents": { + "type": "number" + } + }, + "required": [ + "id", + "status" + ] + } + ] } } } @@ -95,7 +164,19 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/new_agent_policy" + "title": "NewAgentPolicy", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "description": { + "type": "string" + } + } } } } @@ -103,7 +184,7 @@ "security": [], "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] } @@ -131,7 +212,7 @@ "type": "object", "properties": { "item": { - "$ref": "#/components/schemas/agent_policy" + "$ref": "#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item" } }, "required": [ @@ -158,7 +239,7 @@ "type": "object", "properties": { "item": { - "$ref": "#/components/schemas/agent_policy" + "$ref": "#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item" } }, "required": [ @@ -174,14 +255,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/new_agent_policy" + "$ref": "#/paths/~1agent_policies/post/requestBody/content/application~1json/schema" } } } }, "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] } @@ -209,7 +290,7 @@ "type": "object", "properties": { "item": { - "$ref": "#/components/schemas/agent_policy" + "$ref": "#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item" } }, "required": [ @@ -294,7 +375,7 @@ }, "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] }, @@ -405,7 +486,7 @@ "operationId": "post-fleet-agents-agentId-acks", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ], "requestBody": { @@ -488,7 +569,7 @@ "operationId": "post-fleet-agents-agentId-checkin", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ], "security": [ @@ -503,12 +584,69 @@ "type": "object", "properties": { "local_metadata": { - "$ref": "#/components/schemas/agent_metadata" + "title": "AgentMetadata", + "type": "object" }, "events": { "type": "array", "items": { - "$ref": "#/components/schemas/new_agent_event" + "title": "NewAgentEvent", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "STATE", + "ERROR", + "ACTION_RESULT", + "ACTION" + ] + }, + "subtype": { + "type": "string", + "enum": [ + "RUNNING", + "STARTING", + "IN_PROGRESS", + "CONFIG", + "FAILED", + "STOPPING", + "STOPPED", + "DEGRADED", + "DATA_DUMP", + "ACKNOWLEDGED", + "UNKNOWN" + ] + }, + "timestamp": { + "type": "string" + }, + "message": { + "type": "string" + }, + "payload": { + "type": "string" + }, + "agent_id": { + "type": "string" + }, + "policy_id": { + "type": "string" + }, + "stream_id": { + "type": "string" + }, + "action_id": { + "type": "string" + } + }, + "required": [ + "type", + "subtype", + "timestamp", + "message", + "agent_id" + ] } } } @@ -554,7 +692,7 @@ "operationId": "post-fleet-agents-unenroll", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ], "requestBody": { @@ -593,7 +731,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/upgrade_agent" + "$ref": "#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema" } } } @@ -603,7 +741,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/upgrade_agent" + "$ref": "#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema" } } } @@ -612,7 +750,7 @@ "operationId": "post-fleet-agents-upgrade", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ], "requestBody": { @@ -620,7 +758,34 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/upgrade_agent" + "title": "UpgradeAgent", + "oneOf": [ + { + "type": "object", + "properties": { + "version": { + "type": "string" + } + }, + "required": [ + "version" + ] + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "source_uri": { + "type": "string" + } + }, + "required": [ + "version" + ] + } + ] } } } @@ -637,7 +802,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/bulk_upgrade_agents" + "$ref": "#/paths/~1agents~1bulk_upgrade/post/requestBody/content/application~1json/schema" } } } @@ -647,7 +812,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/upgrade_agent" + "$ref": "#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema" } } } @@ -656,7 +821,7 @@ "operationId": "post-fleet-agents-bulk-upgrade", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ], "requestBody": { @@ -664,7 +829,66 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/bulk_upgrade_agents" + "title": "BulkUpgradeAgents", + "oneOf": [ + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "agents": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "version", + "agents" + ] + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "source_uri": { + "type": "string" + }, + "agents": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "version", + "agents" + ] + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "source_uri": { + "type": "string" + }, + "agents": { + "type": "string" + } + }, + "required": [ + "version", + "agents" + ] + } + ] } } } @@ -687,7 +911,106 @@ "type": "string" }, "item": { - "$ref": "#/components/schemas/agent" + "title": "Agent", + "type": "object", + "properties": { + "type": { + "type": "string", + "title": "AgentType", + "enum": [ + "PERMANENT", + "EPHEMERAL", + "TEMPORARY" + ] + }, + "active": { + "type": "boolean" + }, + "enrolled_at": { + "type": "string" + }, + "unenrolled_at": { + "type": "string" + }, + "unenrollment_started_at": { + "type": "string" + }, + "shared_id": { + "type": "string", + "deprecated": true + }, + "access_api_key_id": { + "type": "string" + }, + "default_api_key_id": { + "type": "string" + }, + "policy_id": { + "type": "string" + }, + "policy_revision": { + "type": "number" + }, + "last_checkin": { + "type": "string" + }, + "user_provided_metadata": { + "$ref": "#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata" + }, + "local_metadata": { + "$ref": "#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata" + }, + "id": { + "type": "string" + }, + "current_error_events": { + "type": "array", + "items": { + "title": "AgentEvent", + "allOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + { + "$ref": "#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/events/items" + } + ] + } + }, + "access_api_key": { + "type": "string" + }, + "status": { + "type": "string", + "title": "AgentStatus", + "enum": [ + "offline", + "error", + "online", + "inactive", + "warning" + ] + }, + "default_api_key": { + "type": "string" + } + }, + "required": [ + "type", + "active", + "enrolled_at", + "id", + "current_error_events", + "status" + ] } } } @@ -698,7 +1021,7 @@ "operationId": "post-fleet-agents-enroll", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ], "requestBody": { @@ -716,7 +1039,8 @@ ] }, "shared_id": { - "type": "string" + "type": "string", + "deprecated": true }, "metadata": { "type": "object", @@ -726,10 +1050,10 @@ ], "properties": { "local": { - "$ref": "#/components/schemas/agent_metadata" + "$ref": "#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata" }, "user_provided": { - "$ref": "#/components/schemas/agent_metadata" + "$ref": "#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata" } } } @@ -826,7 +1150,7 @@ }, "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] } @@ -846,7 +1170,7 @@ "operationId": "post-fleet-enrollment-api-keys", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] } @@ -875,7 +1199,7 @@ "operationId": "delete-fleet-enrollment-api-keys-keyId", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] } @@ -930,7 +1254,51 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/search_result" + "title": "SearchResult", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "download": { + "type": "string" + }, + "icons": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "version": { + "type": "string" + }, + "status": { + "type": "string" + }, + "savedObject": { + "type": "object" + } + }, + "required": [ + "description", + "download", + "icons", + "name", + "path", + "title", + "type", + "version", + "status" + ] } } } @@ -956,7 +1324,182 @@ { "properties": { "response": { - "$ref": "#/components/schemas/package_info" + "title": "PackageInfo", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": "string" + }, + "version": { + "type": "string" + }, + "readme": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + }, + "categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "requirement": { + "oneOf": [ + { + "properties": { + "kibana": { + "type": "object", + "properties": { + "versions": { + "type": "string" + } + } + } + } + }, + { + "properties": { + "elasticsearch": { + "type": "object", + "properties": { + "versions": { + "type": "string" + } + } + } + } + } + ], + "type": "object" + }, + "screenshots": { + "type": "array", + "items": { + "type": "object", + "properties": { + "src": { + "type": "string" + }, + "path": { + "type": "string" + }, + "title": { + "type": "string" + }, + "size": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "src", + "path" + ] + } + }, + "icons": { + "type": "array", + "items": { + "type": "string" + } + }, + "assets": { + "type": "array", + "items": { + "type": "string" + } + }, + "internal": { + "type": "boolean" + }, + "format_version": { + "type": "string" + }, + "data_streams": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "ingeset_pipeline": { + "type": "string" + }, + "vars": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "default": { + "type": "string" + } + }, + "required": [ + "name", + "default" + ] + } + }, + "type": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "title", + "name", + "release", + "ingeset_pipeline", + "type", + "package" + ] + } + }, + "download": { + "type": "string" + }, + "path": { + "type": "string" + }, + "removable": { + "type": "boolean" + } + }, + "required": [ + "name", + "title", + "version", + "description", + "type", + "categories", + "requirement", + "assets", + "format_version", + "download", + "path" + ] } } }, @@ -1043,7 +1586,7 @@ "description": "", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] }, @@ -1088,7 +1631,7 @@ "operationId": "post-epm-delete-pkgkey", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] } @@ -1136,7 +1679,7 @@ "operationId": "put-fleet-agents-agentId", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] }, @@ -1147,7 +1690,7 @@ "operationId": "delete-fleet-agents-agentId", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] } @@ -1185,7 +1728,7 @@ "items": { "type": "array", "items": { - "$ref": "#/components/schemas/package_policy" + "$ref": "#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item" } }, "total": { @@ -1223,14 +1766,96 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/new_package_policy" + "title": "NewPackagePolicy", + "type": "object", + "description": "", + "properties": { + "enabled": { + "type": "boolean" + }, + "package": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "name", + "version", + "title" + ] + }, + "namespace": { + "type": "string" + }, + "output_id": { + "type": "string" + }, + "inputs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "processors": { + "type": "array", + "items": { + "type": "string" + } + }, + "streams": { + "type": "array", + "items": {} + }, + "config": { + "type": "object" + }, + "vars": { + "type": "object" + } + }, + "required": [ + "type", + "enabled", + "streams" + ] + } + }, + "policy_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "output_id", + "inputs", + "policy_id", + "name" + ] } } } }, "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] } @@ -1248,7 +1873,31 @@ "type": "object", "properties": { "item": { - "$ref": "#/components/schemas/package_policy" + "title": "PackagePolicy", + "allOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "revision": { + "type": "number" + }, + "inputs": { + "type": "array", + "items": {} + } + }, + "required": [ + "id", + "revision" + ] + }, + { + "$ref": "#/paths/~1package_policies/post/requestBody/content/application~1json/schema" + } + ] } }, "required": [ @@ -1278,7 +1927,20 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/update_package_policy" + "title": "UpdatePackagePolicy", + "allOf": [ + { + "type": "object", + "properties": { + "version": { + "type": "string" + } + } + }, + { + "$ref": "#/paths/~1package_policies/post/requestBody/content/application~1json/schema" + } + ] } } } @@ -1292,7 +1954,7 @@ "type": "object", "properties": { "item": { - "$ref": "#/components/schemas/package_policy" + "$ref": "#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item" }, "sucess": { "type": "boolean" @@ -1309,7 +1971,7 @@ }, "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "$ref": "#/paths/~1setup/post/parameters/0" } ] } @@ -1353,7 +2015,12 @@ "operationId": "post-setup", "parameters": [ { - "$ref": "#/components/parameters/kbn_xsrf" + "schema": { + "type": "string" + }, + "in": "header", + "name": "kbn-xsrf", + "required": true } ] } @@ -1377,732 +2044,6 @@ "in": "header", "description": "e.g. Authorization: ApiKey base64AccessApiKey" } - }, - "parameters": { - "page_size": { - "name": "perPage", - "in": "query", - "description": "The number of items to return", - "required": false, - "schema": { - "type": "integer", - "default": 50 - } - }, - "page_index": { - "name": "page", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "default": 1 - } - }, - "kuery": { - "name": "kuery", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - "kbn_xsrf": { - "schema": { - "type": "string" - }, - "in": "header", - "name": "kbn-xsrf", - "required": true - } - }, - "schemas": { - "new_agent_policy": { - "title": "NewAgentPolicy", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "namespace": { - "type": "string" - }, - "description": { - "type": "string" - } - } - }, - "new_package_policy": { - "title": "NewPackagePolicy", - "type": "object", - "description": "", - "properties": { - "enabled": { - "type": "boolean" - }, - "package": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "version": { - "type": "string" - }, - "title": { - "type": "string" - } - }, - "required": [ - "name", - "version", - "title" - ] - }, - "namespace": { - "type": "string" - }, - "output_id": { - "type": "string" - }, - "inputs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "processors": { - "type": "array", - "items": { - "type": "string" - } - }, - "streams": { - "type": "array", - "items": {} - }, - "config": { - "type": "object" - }, - "vars": { - "type": "object" - } - }, - "required": [ - "type", - "enabled", - "streams" - ] - } - }, - "policy_id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "required": [ - "output_id", - "inputs", - "policy_id", - "name" - ] - }, - "package_policy": { - "title": "PackagePolicy", - "allOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "revision": { - "type": "number" - }, - "inputs": { - "type": "array", - "items": {} - } - }, - "required": [ - "id", - "revision" - ] - }, - { - "$ref": "#/components/schemas/new_package_policy" - } - ] - }, - "agent_policy": { - "allOf": [ - { - "$ref": "#/components/schemas/new_agent_policy" - }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "packagePolicies": { - "oneOf": [ - { - "items": { - "type": "string" - } - }, - { - "items": { - "$ref": "#/components/schemas/package_policy" - } - } - ], - "type": "array" - }, - "updated_on": { - "type": "string", - "format": "date-time" - }, - "updated_by": { - "type": "string" - }, - "revision": { - "type": "number" - }, - "agents": { - "type": "number" - } - }, - "required": [ - "id", - "status" - ] - } - ] - }, - "agent_metadata": { - "title": "AgentMetadata", - "type": "object" - }, - "new_agent_event": { - "title": "NewAgentEvent", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "STATE", - "ERROR", - "ACTION_RESULT", - "ACTION" - ] - }, - "subtype": { - "type": "string", - "enum": [ - "RUNNING", - "STARTING", - "IN_PROGRESS", - "CONFIG", - "FAILED", - "STOPPING", - "STOPPED", - "DEGRADED", - "DATA_DUMP", - "ACKNOWLEDGED", - "UNKNOWN" - ] - }, - "timestamp": { - "type": "string" - }, - "message": { - "type": "string" - }, - "payload": { - "type": "string" - }, - "agent_id": { - "type": "string" - }, - "policy_id": { - "type": "string" - }, - "stream_id": { - "type": "string" - }, - "action_id": { - "type": "string" - } - }, - "required": [ - "type", - "subtype", - "timestamp", - "message", - "agent_id" - ] - }, - "upgrade_agent": { - "title": "UpgradeAgent", - "oneOf": [ - { - "type": "object", - "properties": { - "version": { - "type": "string" - } - }, - "required": [ - "version" - ] - }, - { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "source_uri": { - "type": "string" - } - }, - "required": [ - "version" - ] - } - ] - }, - "bulk_upgrade_agents": { - "title": "BulkUpgradeAgents", - "oneOf": [ - { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "agents": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "version", - "agents" - ] - }, - { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "source_uri": { - "type": "string" - }, - "agents": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "version", - "agents" - ] - }, - { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "source_uri": { - "type": "string" - }, - "agents": { - "type": "string" - } - }, - "required": [ - "version", - "agents" - ] - } - ] - }, - "agent_type": { - "type": "string", - "title": "AgentType", - "enum": [ - "PERMANENT", - "EPHEMERAL", - "TEMPORARY" - ] - }, - "agent_event": { - "title": "AgentEvent", - "allOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } - }, - "required": [ - "id" - ] - }, - { - "$ref": "#/components/schemas/new_agent_event" - } - ] - }, - "agent_status": { - "type": "string", - "title": "AgentStatus", - "enum": [ - "offline", - "error", - "online", - "inactive", - "warning" - ] - }, - "agent": { - "title": "Agent", - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/agent_type" - }, - "active": { - "type": "boolean" - }, - "enrolled_at": { - "type": "string" - }, - "unenrolled_at": { - "type": "string" - }, - "unenrollment_started_at": { - "type": "string" - }, - "shared_id": { - "type": "string" - }, - "access_api_key_id": { - "type": "string" - }, - "default_api_key_id": { - "type": "string" - }, - "policy_id": { - "type": "string" - }, - "policy_revision": { - "type": "number" - }, - "last_checkin": { - "type": "string" - }, - "user_provided_metadata": { - "$ref": "#/components/schemas/agent_metadata" - }, - "local_metadata": { - "$ref": "#/components/schemas/agent_metadata" - }, - "id": { - "type": "string" - }, - "current_error_events": { - "type": "array", - "items": { - "$ref": "#/components/schemas/agent_event" - } - }, - "access_api_key": { - "type": "string" - }, - "status": { - "$ref": "#/components/schemas/agent_status" - }, - "default_api_key": { - "type": "string" - } - }, - "required": [ - "type", - "active", - "enrolled_at", - "id", - "current_error_events", - "status" - ] - }, - "search_result": { - "title": "SearchResult", - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "download": { - "type": "string" - }, - "icons": { - "type": "string" - }, - "name": { - "type": "string" - }, - "path": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - }, - "version": { - "type": "string" - }, - "status": { - "type": "string" - }, - "savedObject": { - "type": "object" - } - }, - "required": [ - "description", - "download", - "icons", - "name", - "path", - "title", - "type", - "version", - "status" - ] - }, - "package_info": { - "title": "PackageInfo", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "version": { - "type": "string" - }, - "readme": { - "type": "string" - }, - "description": { - "type": "string" - }, - "type": { - "type": "string" - }, - "categories": { - "type": "array", - "items": { - "type": "string" - } - }, - "requirement": { - "oneOf": [ - { - "properties": { - "kibana": { - "type": "object", - "properties": { - "versions": { - "type": "string" - } - } - } - } - }, - { - "properties": { - "elasticsearch": { - "type": "object", - "properties": { - "versions": { - "type": "string" - } - } - } - } - } - ], - "type": "object" - }, - "screenshots": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "path": { - "type": "string" - }, - "title": { - "type": "string" - }, - "size": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src", - "path" - ] - } - }, - "icons": { - "type": "array", - "items": { - "type": "string" - } - }, - "assets": { - "type": "array", - "items": { - "type": "string" - } - }, - "internal": { - "type": "boolean" - }, - "format_version": { - "type": "string" - }, - "data_streams": { - "type": "array", - "items": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "name": { - "type": "string" - }, - "release": { - "type": "string" - }, - "ingeset_pipeline": { - "type": "string" - }, - "vars": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "default": { - "type": "string" - } - }, - "required": [ - "name", - "default" - ] - } - }, - "type": { - "type": "string" - }, - "package": { - "type": "string" - } - }, - "required": [ - "title", - "name", - "release", - "ingeset_pipeline", - "type", - "package" - ] - } - }, - "download": { - "type": "string" - }, - "path": { - "type": "string" - }, - "removable": { - "type": "boolean" - } - }, - "required": [ - "name", - "title", - "version", - "description", - "type", - "categories", - "requirement", - "assets", - "format_version", - "download", - "path" - ] - }, - "update_package_policy": { - "title": "UpdatePackagePolicy", - "allOf": [ - { - "type": "object", - "properties": { - "version": { - "type": "string" - } - } - }, - { - "$ref": "#/components/schemas/new_package_policy" - } - ] - } } }, "security": [ @@ -2110,4 +2051,4 @@ "basicAuth": [] } ] -} \ No newline at end of file +} diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 05b5b239dc980..9461927bb09b8 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -25,7 +25,7 @@ paths: items: type: array items: - $ref: '#/components/schemas/agent_policy' + $ref: '#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item' total: type: number page: @@ -39,9 +39,24 @@ paths: - perPage operationId: agent-policy-list parameters: - - $ref: '#/components/parameters/page_size' - - $ref: '#/components/parameters/page_index' - - $ref: '#/components/parameters/kuery' + - name: perPage + in: query + description: The number of items to return + required: false + schema: + type: integer + default: 50 + - name: page + in: query + required: false + schema: + type: integer + default: 1 + - name: kuery + in: query + required: false + schema: + type: string description: '' post: summary: Agent policy - Create @@ -55,16 +70,53 @@ paths: type: object properties: item: - $ref: '#/components/schemas/agent_policy' + allOf: + - $ref: '#/paths/~1agent_policies/post/requestBody/content/application~1json/schema' + - type: object + properties: + id: + type: string + status: + type: string + enum: + - active + - inactive + packagePolicies: + oneOf: + - items: + type: string + - items: + $ref: '#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item' + type: array + updated_on: + type: string + format: date-time + updated_by: + type: string + revision: + type: number + agents: + type: number + required: + - id + - status operationId: post-agent-policy requestBody: content: application/json: schema: - $ref: '#/components/schemas/new_agent_policy' + title: NewAgentPolicy + type: object + properties: + name: + type: string + namespace: + type: string + description: + type: string security: [] parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' '/agent_policies/{agentPolicyId}': parameters: - schema: @@ -84,7 +136,7 @@ paths: type: object properties: item: - $ref: '#/components/schemas/agent_policy' + $ref: '#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item' required: - item operationId: agent-policy-info @@ -102,7 +154,7 @@ paths: type: object properties: item: - $ref: '#/components/schemas/agent_policy' + $ref: '#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item' required: - item operationId: put-agent-policy-agentPolicyId @@ -110,9 +162,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/new_agent_policy' + $ref: '#/paths/~1agent_policies/post/requestBody/content/application~1json/schema' parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' '/agent_policies/{agentPolicyId}/copy': parameters: - schema: @@ -132,7 +184,7 @@ paths: type: object properties: item: - $ref: '#/components/schemas/agent_policy' + $ref: '#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item' required: - item requestBody: @@ -181,7 +233,7 @@ paths: items: type: string parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' parameters: [] /agent-status: get: @@ -251,7 +303,7 @@ paths: - action operationId: post-fleet-agents-agentId-acks parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' requestBody: content: application/json: @@ -304,7 +356,7 @@ paths: - type operationId: post-fleet-agents-agentId-checkin parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' security: - Access API Key: [] requestBody: @@ -314,11 +366,55 @@ paths: type: object properties: local_metadata: - $ref: '#/components/schemas/agent_metadata' + title: AgentMetadata + type: object events: type: array items: - $ref: '#/components/schemas/new_agent_event' + title: NewAgentEvent + type: object + properties: + type: + type: string + enum: + - STATE + - ERROR + - ACTION_RESULT + - ACTION + subtype: + type: string + enum: + - RUNNING + - STARTING + - IN_PROGRESS + - CONFIG + - FAILED + - STOPPING + - STOPPED + - DEGRADED + - DATA_DUMP + - ACKNOWLEDGED + - UNKNOWN + timestamp: + type: string + message: + type: string + payload: + type: string + agent_id: + type: string + policy_id: + type: string + stream_id: + type: string + action_id: + type: string + required: + - type + - subtype + - timestamp + - message + - agent_id '/agents/{agentId}/events': parameters: - schema: @@ -344,7 +440,7 @@ paths: responses: {} operationId: post-fleet-agents-unenroll parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' requestBody: content: application/json: @@ -369,22 +465,37 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/upgrade_agent' + $ref: '#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema' '400': description: BAD REQUEST content: application/json: schema: - $ref: '#/components/schemas/upgrade_agent' + $ref: '#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema' operationId: post-fleet-agents-upgrade parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/upgrade_agent' + title: UpgradeAgent + oneOf: + - type: object + properties: + version: + type: string + required: + - version + - type: object + properties: + version: + type: string + source_uri: + type: string + required: + - version /agents/bulk_upgrade: post: summary: Fleet - Agent - Bulk Upgrade @@ -395,22 +506,58 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/bulk_upgrade_agents' + $ref: '#/paths/~1agents~1bulk_upgrade/post/requestBody/content/application~1json/schema' '400': description: BAD REQUEST content: application/json: schema: - $ref: '#/components/schemas/upgrade_agent' + $ref: '#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema' operationId: post-fleet-agents-bulk-upgrade parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/bulk_upgrade_agents' + title: BulkUpgradeAgents + oneOf: + - type: object + properties: + version: + type: string + agents: + type: array + items: + type: string + required: + - version + - agents + - type: object + properties: + version: + type: string + source_uri: + type: string + agents: + type: array + items: + type: string + required: + - version + - agents + - type: object + properties: + version: + type: string + source_uri: + type: string + agents: + type: string + required: + - version + - agents /agents/enroll: post: summary: Fleet - Agent - Enroll @@ -426,10 +573,78 @@ paths: action: type: string item: - $ref: '#/components/schemas/agent' + title: Agent + type: object + properties: + type: + type: string + title: AgentType + enum: + - PERMANENT + - EPHEMERAL + - TEMPORARY + active: + type: boolean + enrolled_at: + type: string + unenrolled_at: + type: string + unenrollment_started_at: + type: string + shared_id: + type: string + deprecated: true + access_api_key_id: + type: string + default_api_key_id: + type: string + policy_id: + type: string + policy_revision: + type: number + last_checkin: + type: string + user_provided_metadata: + $ref: '#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata' + local_metadata: + $ref: '#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata' + id: + type: string + current_error_events: + type: array + items: + title: AgentEvent + allOf: + - type: object + properties: + id: + type: string + required: + - id + - $ref: '#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/events/items' + access_api_key: + type: string + status: + type: string + title: AgentStatus + enum: + - offline + - error + - online + - inactive + - warning + default_api_key: + type: string + required: + - type + - active + - enrolled_at + - id + - current_error_events + - status operationId: post-fleet-agents-enroll parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' requestBody: content: application/json: @@ -444,6 +659,7 @@ paths: - TEMPORARY shared_id: type: string + deprecated: true metadata: type: object required: @@ -451,9 +667,9 @@ paths: - user_provided properties: local: - $ref: '#/components/schemas/agent_metadata' + $ref: '#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata' user_provided: - $ref: '#/components/schemas/agent_metadata' + $ref: '#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata' required: - type - metadata @@ -507,7 +723,7 @@ paths: - admin_username - admin_password parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' /enrollment-api-keys: get: summary: Enrollment - List @@ -521,7 +737,7 @@ paths: responses: {} operationId: post-fleet-enrollment-api-keys parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' '/enrollment-api-keys/{keyId}': parameters: - schema: @@ -540,7 +756,7 @@ paths: responses: {} operationId: delete-fleet-enrollment-api-keys-keyId parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' /epm/categories: get: summary: EPM - Categories @@ -578,7 +794,39 @@ paths: schema: type: array items: - $ref: '#/components/schemas/search_result' + title: SearchResult + type: object + properties: + description: + type: string + download: + type: string + icons: + type: string + name: + type: string + path: + type: string + title: + type: string + type: + type: string + version: + type: string + status: + type: string + savedObject: + type: object + required: + - description + - download + - icons + - name + - path + - title + - type + - version + - status operationId: get-epm-list parameters: [] '/epm/packages/{pkgkey}': @@ -595,7 +843,124 @@ paths: allOf: - properties: response: - $ref: '#/components/schemas/package_info' + title: PackageInfo + type: object + properties: + name: + type: string + title: + type: string + version: + type: string + readme: + type: string + description: + type: string + type: + type: string + categories: + type: array + items: + type: string + requirement: + oneOf: + - properties: + kibana: + type: object + properties: + versions: + type: string + - properties: + elasticsearch: + type: object + properties: + versions: + type: string + type: object + screenshots: + type: array + items: + type: object + properties: + src: + type: string + path: + type: string + title: + type: string + size: + type: string + type: + type: string + required: + - src + - path + icons: + type: array + items: + type: string + assets: + type: array + items: + type: string + internal: + type: boolean + format_version: + type: string + data_streams: + type: array + items: + type: object + properties: + title: + type: string + name: + type: string + release: + type: string + ingeset_pipeline: + type: string + vars: + type: array + items: + type: object + properties: + name: + type: string + default: + type: string + required: + - name + - default + type: + type: string + package: + type: string + required: + - title + - name + - release + - ingeset_pipeline + - type + - package + download: + type: string + path: + type: string + removable: + type: boolean + required: + - name + - title + - version + - description + - type + - categories + - requirement + - assets + - format_version + - download + - path - properties: status: type: string @@ -644,7 +1009,7 @@ paths: operationId: post-epm-install-pkgkey description: '' parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' delete: summary: EPM - Packages - Delete tags: [] @@ -672,7 +1037,7 @@ paths: - response operationId: post-epm-delete-pkgkey parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' '/agents/{agentId}': parameters: - schema: @@ -702,14 +1067,14 @@ paths: responses: {} operationId: put-fleet-agents-agentId parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' delete: summary: Fleet - Agent - Delete tags: [] responses: {} operationId: delete-fleet-agents-agentId parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' '/install/{osType}': parameters: - schema: @@ -737,7 +1102,7 @@ paths: items: type: array items: - $ref: '#/components/schemas/package_policy' + $ref: '#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item' total: type: number page: @@ -760,9 +1125,66 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/new_package_policy' + title: NewPackagePolicy + type: object + description: '' + properties: + enabled: + type: boolean + package: + type: object + properties: + name: + type: string + version: + type: string + title: + type: string + required: + - name + - version + - title + namespace: + type: string + output_id: + type: string + inputs: + type: array + items: + type: object + properties: + type: + type: string + enabled: + type: boolean + processors: + type: array + items: + type: string + streams: + type: array + items: {} + config: + type: object + vars: + type: object + required: + - type + - enabled + - streams + policy_id: + type: string + name: + type: string + description: + type: string + required: + - output_id + - inputs + - policy_id + - name parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' '/package_policies/{packagePolicyId}': get: summary: PackagePolicies - Info @@ -776,7 +1198,21 @@ paths: type: object properties: item: - $ref: '#/components/schemas/package_policy' + title: PackagePolicy + allOf: + - type: object + properties: + id: + type: string + revision: + type: number + inputs: + type: array + items: {} + required: + - id + - revision + - $ref: '#/paths/~1package_policies/post/requestBody/content/application~1json/schema' required: - item operationId: get-packagePolicies-packagePolicyId @@ -793,7 +1229,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/update_package_policy' + title: UpdatePackagePolicy + allOf: + - type: object + properties: + version: + type: string + - $ref: '#/paths/~1package_policies/post/requestBody/content/application~1json/schema' responses: '200': description: OK @@ -803,14 +1245,14 @@ paths: type: object properties: item: - $ref: '#/components/schemas/package_policy' + $ref: '#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item' sucess: type: boolean required: - item - sucess parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/paths/~1setup/post/parameters/0' /setup: post: summary: Ingest Manager - Setup @@ -836,7 +1278,11 @@ paths: type: string operationId: post-setup parameters: - - $ref: '#/components/parameters/kbn_xsrf' + - schema: + type: string + in: header + name: kbn-xsrf + required: true components: securitySchemes: basicAuth: @@ -852,489 +1298,5 @@ components: type: apiKey in: header description: 'e.g. Authorization: ApiKey base64AccessApiKey' - parameters: - page_size: - name: perPage - in: query - description: The number of items to return - required: false - schema: - type: integer - default: 50 - page_index: - name: page - in: query - required: false - schema: - type: integer - default: 1 - kuery: - name: kuery - in: query - required: false - schema: - type: string - kbn_xsrf: - schema: - type: string - in: header - name: kbn-xsrf - required: true - schemas: - new_agent_policy: - title: NewAgentPolicy - type: object - properties: - name: - type: string - namespace: - type: string - description: - type: string - new_package_policy: - title: NewPackagePolicy - type: object - description: '' - properties: - enabled: - type: boolean - package: - type: object - properties: - name: - type: string - version: - type: string - title: - type: string - required: - - name - - version - - title - namespace: - type: string - output_id: - type: string - inputs: - type: array - items: - type: object - properties: - type: - type: string - enabled: - type: boolean - processors: - type: array - items: - type: string - streams: - type: array - items: {} - config: - type: object - vars: - type: object - required: - - type - - enabled - - streams - policy_id: - type: string - name: - type: string - description: - type: string - required: - - output_id - - inputs - - policy_id - - name - package_policy: - title: PackagePolicy - allOf: - - type: object - properties: - id: - type: string - revision: - type: number - inputs: - type: array - items: {} - required: - - id - - revision - - $ref: '#/components/schemas/new_package_policy' - agent_policy: - allOf: - - $ref: '#/components/schemas/new_agent_policy' - - type: object - properties: - id: - type: string - status: - type: string - enum: - - active - - inactive - packagePolicies: - oneOf: - - items: - type: string - - items: - $ref: '#/components/schemas/package_policy' - type: array - updated_on: - type: string - format: date-time - updated_by: - type: string - revision: - type: number - agents: - type: number - required: - - id - - status - agent_metadata: - title: AgentMetadata - type: object - new_agent_event: - title: NewAgentEvent - type: object - properties: - type: - type: string - enum: - - STATE - - ERROR - - ACTION_RESULT - - ACTION - subtype: - type: string - enum: - - RUNNING - - STARTING - - IN_PROGRESS - - CONFIG - - FAILED - - STOPPING - - STOPPED - - DEGRADED - - DATA_DUMP - - ACKNOWLEDGED - - UNKNOWN - timestamp: - type: string - message: - type: string - payload: - type: string - agent_id: - type: string - policy_id: - type: string - stream_id: - type: string - action_id: - type: string - required: - - type - - subtype - - timestamp - - message - - agent_id - upgrade_agent: - title: UpgradeAgent - oneOf: - - type: object - properties: - version: - type: string - required: - - version - - type: object - properties: - version: - type: string - source_uri: - type: string - required: - - version - bulk_upgrade_agents: - title: BulkUpgradeAgents - oneOf: - - type: object - properties: - version: - type: string - agents: - type: array - items: - type: string - required: - - version - - agents - - type: object - properties: - version: - type: string - source_uri: - type: string - agents: - type: array - items: - type: string - required: - - version - - agents - - type: object - properties: - version: - type: string - source_uri: - type: string - agents: - type: string - required: - - version - - agents - agent_type: - type: string - title: AgentType - enum: - - PERMANENT - - EPHEMERAL - - TEMPORARY - agent_event: - title: AgentEvent - allOf: - - type: object - properties: - id: - type: string - required: - - id - - $ref: '#/components/schemas/new_agent_event' - agent_status: - type: string - title: AgentStatus - enum: - - offline - - error - - online - - inactive - - warning - agent: - title: Agent - type: object - properties: - type: - $ref: '#/components/schemas/agent_type' - active: - type: boolean - enrolled_at: - type: string - unenrolled_at: - type: string - unenrollment_started_at: - type: string - shared_id: - type: string - access_api_key_id: - type: string - default_api_key_id: - type: string - policy_id: - type: string - policy_revision: - type: number - last_checkin: - type: string - user_provided_metadata: - $ref: '#/components/schemas/agent_metadata' - local_metadata: - $ref: '#/components/schemas/agent_metadata' - id: - type: string - current_error_events: - type: array - items: - $ref: '#/components/schemas/agent_event' - access_api_key: - type: string - status: - $ref: '#/components/schemas/agent_status' - default_api_key: - type: string - required: - - type - - active - - enrolled_at - - id - - current_error_events - - status - search_result: - title: SearchResult - type: object - properties: - description: - type: string - download: - type: string - icons: - type: string - name: - type: string - path: - type: string - title: - type: string - type: - type: string - version: - type: string - status: - type: string - savedObject: - type: object - required: - - description - - download - - icons - - name - - path - - title - - type - - version - - status - package_info: - title: PackageInfo - type: object - properties: - name: - type: string - title: - type: string - version: - type: string - readme: - type: string - description: - type: string - type: - type: string - categories: - type: array - items: - type: string - requirement: - oneOf: - - properties: - kibana: - type: object - properties: - versions: - type: string - - properties: - elasticsearch: - type: object - properties: - versions: - type: string - type: object - screenshots: - type: array - items: - type: object - properties: - src: - type: string - path: - type: string - title: - type: string - size: - type: string - type: - type: string - required: - - src - - path - icons: - type: array - items: - type: string - assets: - type: array - items: - type: string - internal: - type: boolean - format_version: - type: string - data_streams: - type: array - items: - type: object - properties: - title: - type: string - name: - type: string - release: - type: string - ingeset_pipeline: - type: string - vars: - type: array - items: - type: object - properties: - name: - type: string - default: - type: string - required: - - name - - default - type: - type: string - package: - type: string - required: - - title - - name - - release - - ingeset_pipeline - - type - - package - download: - type: string - path: - type: string - removable: - type: boolean - required: - - name - - title - - version - - description - - type - - categories - - requirement - - assets - - format_version - - download - - path - update_package_policy: - title: UpdatePackagePolicy - allOf: - - type: object - properties: - version: - type: string - - $ref: '#/components/schemas/new_package_policy' security: - basicAuth: [] diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/agent.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent.yaml index df106093a8d8d..a2647b71c70cc 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/agent.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/agent.yaml @@ -13,6 +13,7 @@ properties: type: string shared_id: type: string + deprecated: true access_api_key_id: type: string default_api_key_id: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@enroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@enroll.yaml index a0c1c8c28e721..1946a65e33fdc 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@enroll.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@enroll.yaml @@ -30,6 +30,7 @@ post: - TEMPORARY shared_id: type: string + deprecated: true metadata: type: object required: diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index 59fab14f90e6e..b59249da2dd34 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -130,7 +130,6 @@ interface AgentBase { unenrollment_started_at?: string; upgraded_at?: string; upgrade_started_at?: string; - shared_id?: string; access_api_key_id?: string; default_api_key?: string; default_api_key_id?: string; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index f758ca0921a08..925ed4b8b1638 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -62,7 +62,6 @@ export interface PostAgentCheckinResponse { export interface PostAgentEnrollRequest { body: { type: AgentType; - shared_id?: string; metadata: { local: Record; user_provided: Record; diff --git a/x-pack/plugins/fleet/dev_docs/api/agents_enroll.md b/x-pack/plugins/fleet/dev_docs/api/agents_enroll.md index 977b3029371ba..7dd56338b31fa 100644 --- a/x-pack/plugins/fleet/dev_docs/api/agents_enroll.md +++ b/x-pack/plugins/fleet/dev_docs/api/agents_enroll.md @@ -13,7 +13,6 @@ Enroll agent ## Request body - `type` (Required, string) Agent type should be one of `EPHEMERAL`, `TEMPORARY`, `PERMANENT` -- `shared_id` (Optional, string) An ID for the agent. - `metadata` (Optional, object) Objects with `local` and `user_provided` properties that contain the metadata for an agent. The metadata is a dictionary of strings (example: `"local": { "os": "macos" }`). ## Response code @@ -68,12 +67,3 @@ The API will return a response with a `401` status code and an error if the enro } ``` -The API will return a response with a `400` status code and an error if you enroll an agent with the same `shared_id` than an already active agent: - -```js -{ - "statusCode": 400, - "error": "BadRequest", - "message": "Impossible to enroll an already active agent" -} -``` diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 0cd53a2313d2a..ace18e10115d1 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -220,8 +220,7 @@ export const postAgentEnrollHandler: RequestHandler< { userProvided: request.body.metadata.user_provided, local: request.body.metadata.local, - }, - request.body.shared_id + } ); const body: PostAgentEnrollResponse = { action: 'created', diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 20bbee2b1c791..dcc686e565b8e 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -28,6 +28,7 @@ import { migrateSettingsToV7100, migrateAgentActionToV7100, } from './migrations/to_v7_10_0'; +import { migrateAgentToV7120 } from './migrations/to_v7_12_0'; /* * Saved object types and mappings @@ -67,7 +68,6 @@ const getSavedObjectTypes = ( }, mappings: { properties: { - shared_id: { type: 'keyword' }, type: { type: 'keyword' }, active: { type: 'boolean' }, enrolled_at: { type: 'date' }, @@ -93,6 +93,7 @@ const getSavedObjectTypes = ( }, migrations: { '7.10.0': migrateAgentToV7100, + '7.12.0': migrateAgentToV7120, }, }, [AGENT_ACTION_SAVED_OBJECT_TYPE]: { @@ -385,7 +386,6 @@ export function registerEncryptedSavedObjects( type: AGENT_SAVED_OBJECT_TYPE, attributesToEncrypt: new Set(['default_api_key']), attributesToExcludeFromAAD: new Set([ - 'shared_id', 'type', 'active', 'enrolled_at', diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts new file mode 100644 index 0000000000000..841e56a60091b --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectMigrationFn } from 'kibana/server'; +import { Agent } from '../../types'; + +export const migrateAgentToV7120: SavedObjectMigrationFn = ( + agentDoc +) => { + delete agentDoc.attributes.shared_id; + + return agentDoc; +}; diff --git a/x-pack/plugins/fleet/server/services/agents/enroll.ts b/x-pack/plugins/fleet/server/services/agents/enroll.ts index 39b757b9776ed..113f302d52b45 100644 --- a/x-pack/plugins/fleet/server/services/agents/enroll.ts +++ b/x-pack/plugins/fleet/server/services/agents/enroll.ts @@ -20,26 +20,16 @@ export async function enroll( soClient: SavedObjectsClientContract, type: AgentType, agentPolicyId: string, - metadata?: { local: any; userProvided: any }, - sharedId?: string + metadata?: { local: any; userProvided: any } ): Promise { const agentVersion = metadata?.local?.elastic?.agent?.version; validateAgentVersion(agentVersion); - const existingAgent = sharedId ? await getAgentBySharedId(soClient, sharedId) : null; - - if (existingAgent && existingAgent.active === true) { - throw Boom.badRequest('Impossible to enroll an already active agent'); - } - - const enrolledAt = new Date().toISOString(); - const agentData: AgentSOAttributes = { - shared_id: sharedId, active: true, policy_id: agentPolicyId, type, - enrolled_at: enrolledAt, + enrolled_at: new Date().toISOString(), user_provided_metadata: metadata?.userProvided ?? {}, local_metadata: metadata?.local ?? {}, current_error_events: undefined, @@ -48,25 +38,11 @@ export async function enroll( default_api_key: undefined, }; - let agent; - if (existingAgent) { - await soClient.update(AGENT_SAVED_OBJECT_TYPE, existingAgent.id, agentData, { + const agent = savedObjectToAgent( + await soClient.create(AGENT_SAVED_OBJECT_TYPE, agentData, { refresh: false, - }); - agent = { - ...existingAgent, - ...agentData, - user_provided_metadata: metadata?.userProvided ?? {}, - local_metadata: metadata?.local ?? {}, - current_error_events: [], - } as Agent; - } else { - agent = savedObjectToAgent( - await soClient.create(AGENT_SAVED_OBJECT_TYPE, agentData, { - refresh: false, - }) - ); - } + }) + ); const accessAPIKey = await APIKeyService.generateAccessApiKey(soClient, agent.id); @@ -77,22 +53,6 @@ export async function enroll( return { ...agent, access_api_key: accessAPIKey.key }; } -async function getAgentBySharedId(soClient: SavedObjectsClientContract, sharedId: string) { - const response = await soClient.find({ - type: AGENT_SAVED_OBJECT_TYPE, - searchFields: ['shared_id'], - search: sharedId, - }); - - const agents = response.saved_objects.map(savedObjectToAgent); - - if (agents.length > 0) { - return agents[0]; - } - - return null; -} - export function validateAgentVersion( agentVersion: string, kibanaVersion = appContextService.getKibanaVersion() diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index 3e9262c2a9124..a37002114c771 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -83,6 +83,7 @@ export const PostAgentEnrollRequestBodyJSONSchema = { type: 'object', properties: { type: { type: 'string', enum: ['EPHEMERAL', 'PERMANENT', 'TEMPORARY'] }, + // TODO deprecated should be removed in 8.0.0 shared_id: { type: 'string' }, metadata: { type: 'object', diff --git a/x-pack/test/fleet_api_integration/apis/agents/enroll.ts b/x-pack/test/fleet_api_integration/apis/agents/enroll.ts index c88106eb79cd2..609b28417914e 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/enroll.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/enroll.ts @@ -74,28 +74,6 @@ export default function (providerContext: FtrProviderContext) { .expect(401); }); - it('should not allow to enroll an agent with a shared id if it already exists ', async () => { - const { body: apiResponse } = await supertest - .post(`/api/fleet/agents/enroll`) - .set('kbn-xsrf', 'xxx') - .set( - 'authorization', - `ApiKey ${Buffer.from(`${apiKey.id}:${apiKey.api_key}`).toString('base64')}` - ) - .send({ - shared_id: 'agent2_filebeat', - type: 'PERMANENT', - metadata: { - local: { - elastic: { agent: { version: kibanaVersion } }, - }, - user_provided: {}, - }, - }) - .expect(400); - expect(apiResponse.message).to.match(/Impossible to enroll an already active agent/); - }); - it('should not allow to enroll an agent with a version > kibana', async () => { const { body: apiResponse } = await supertest .post(`/api/fleet/agents/enroll`) diff --git a/x-pack/test/fleet_api_integration/apis/agents/list.ts b/x-pack/test/fleet_api_integration/apis/agents/list.ts index 78a6dbb7d651a..1b3d3e7d32cb7 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/list.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/list.ts @@ -104,14 +104,14 @@ export default function ({ getService }: FtrProviderContext) { .expect(400); }); it('should accept a valid "kuery" value', async () => { - const filter = encodeURIComponent('fleet-agents.shared_id : "agent2_filebeat"'); + const filter = encodeURIComponent('fleet-agents.access_api_key_id : "api-key-2"'); const { body: apiResponse } = await supertest .get(`/api/fleet/agents?kuery=${filter}`) .expect(200); expect(apiResponse.total).to.eql(1); const agent = apiResponse.list[0]; - expect(agent.shared_id).to.eql('agent2_filebeat'); + expect(agent.access_api_key_id).to.eql('api-key-2'); }); }); } diff --git a/x-pack/test/functional/es_archives/fleet/agents/data.json b/x-pack/test/functional/es_archives/fleet/agents/data.json index f204e44b31bc9..ca957e5ae2fed 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/data.json +++ b/x-pack/test/functional/es_archives/fleet/agents/data.json @@ -6,9 +6,8 @@ "source": { "type": "fleet-agents", "fleet-agents": { - "access_api_key_id": "api-key-2", + "access_api_key_id": "api-key-1", "active": true, - "shared_id": "agent1_filebeat", "policy_id": "policy1", "type": "PERMANENT", "local_metadata": {}, @@ -31,7 +30,6 @@ "fleet-agents": { "access_api_key_id": "api-key-2", "active": true, - "shared_id": "agent2_filebeat", "policy_id": "policy1", "type": "PERMANENT", "local_metadata": {}, @@ -54,7 +52,6 @@ "fleet-agents": { "access_api_key_id": "api-key-3", "active": true, - "shared_id": "agent3_metricbeat", "policy_id": "policy1", "type": "PERMANENT", "local_metadata": {}, @@ -77,7 +74,6 @@ "fleet-agents": { "access_api_key_id": "api-key-4", "active": true, - "shared_id": "agent4_metricbeat", "policy_id": "policy1", "type": "PERMANENT", "local_metadata": {}, From 440238b051b7d2b878ff2cfb23b6afa52afd05cb Mon Sep 17 00:00:00 2001 From: Constance Date: Thu, 21 Jan 2021 10:55:48 -0800 Subject: [PATCH 68/83] [App Search] Add generatePath helper for generating engine links (#88782) * Add a generatePath engineName helper to EngineLogic * Create mockEngineValues reusable mock * Update routes + EngineNav & EngineRouter to include ENGINE_PATH in all urls - routes: remove get*Route fns in here as all routes should prefer to use generatePath from EngineLogic moving forward - EngineRouter - add missing canViewEngineDocuments checks - Engine tests - import base mock values + update tests to point directly at files to work around the auto mock * Update AnalyticsRouter to use new routes+generatePath * Update DocumentDetailLogic to use new generatePath + Misc cleanup: - organize imports by shared > AS specific > docs specific - move delete-specific const's to directly before they're used, since they're only used in one place - deconstruct KibanaLogic.values * Update all components using getEngineRoute to use new generatePath + misc import order cleanup - prefer shared > specific groupings * [PR feedback] Change components that override the engineName param to just use default generatePath * [PR feedback] Rename instances of EngineLogic's generatePath to generateEnginePath --- .../app_search/__mocks__/engine_logic.mock.ts | 23 ++++++++++ .../app_search/__mocks__/index.ts | 7 +++ .../analytics/analytics_router.test.tsx | 4 ++ .../components/analytics/analytics_router.tsx | 27 +++++------ .../document_creation_buttons.test.tsx | 8 ++-- .../document_creation_buttons.tsx | 6 +-- .../documents/document_detail_logic.test.ts | 6 +-- .../documents/document_detail_logic.ts | 45 ++++++++++--------- .../components/engine/engine_logic.test.ts | 23 ++++++++++ .../components/engine/engine_logic.ts | 11 +++++ .../components/engine/engine_nav.test.tsx | 5 ++- .../components/engine/engine_nav.tsx | 28 ++++++------ .../components/engine/engine_router.test.tsx | 5 ++- .../components/engine/engine_router.tsx | 7 ++- .../components/recent_api_logs.test.tsx | 5 +-- .../components/recent_api_logs.tsx | 10 ++--- .../components/total_charts.test.tsx | 3 +- .../components/total_charts.tsx | 14 +++--- .../components/engines/engines_table.test.tsx | 3 +- .../components/engines/engines_table.tsx | 7 +-- .../app_search/components/result/result.tsx | 13 ++++-- .../public/applications/app_search/routes.ts | 34 ++++++-------- 22 files changed, 177 insertions(+), 117 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/index.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts new file mode 100644 index 0000000000000..5c327f64d7775 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { generatePath } from 'react-router-dom'; + +export const mockEngineValues = { + engineName: 'some-engine', + // Note: using getters allows us to use `this`, which lets tests + // override engineName and still generate correct engine names + get generateEnginePath() { + return jest.fn((path, pathParams = {}) => + generatePath(path, { engineName: this.engineName, ...pathParams }) + ); + }, + engine: {}, +}; + +jest.mock('../components/engine', () => ({ + EngineLogic: { values: mockEngineValues }, +})); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/index.ts new file mode 100644 index 0000000000000..0b0a85b6fca92 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/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 { mockEngineValues } from './engine_logic.mock'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx index 82d2a6614a32a..aea107a137da1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { setMockValues } from '../../../__mocks__'; +import { mockEngineValues } from '../../__mocks__'; + import React from 'react'; import { shallow } from 'enzyme'; import { Route, Switch } from 'react-router-dom'; @@ -13,6 +16,7 @@ import { AnalyticsRouter } from './'; describe('AnalyticsRouter', () => { // Detailed route testing is better done via E2E tests it('renders', () => { + setMockValues(mockEngineValues); const wrapper = shallow(); expect(wrapper.find(Switch)).toHaveLength(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx index ac5c472a9a388..60c0f2a3fd3e8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx @@ -6,14 +6,13 @@ import React from 'react'; import { Route, Switch, Redirect } from 'react-router-dom'; +import { useValues } from 'kea'; import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { BreadcrumbTrail } from '../../../shared/kibana_chrome/generate_breadcrumbs'; import { NotFound } from '../../../shared/not_found'; import { - getEngineRoute, - ENGINE_PATH, ENGINE_ANALYTICS_PATH, ENGINE_ANALYTICS_TOP_QUERIES_PATH, ENGINE_ANALYTICS_TOP_QUERIES_NO_RESULTS_PATH, @@ -23,6 +22,8 @@ import { ENGINE_ANALYTICS_QUERY_DETAILS_PATH, ENGINE_ANALYTICS_QUERY_DETAIL_PATH, } from '../../routes'; +import { EngineLogic } from '../engine'; + import { ANALYTICS_TITLE, TOP_QUERIES, @@ -31,7 +32,6 @@ import { TOP_QUERIES_WITH_CLICKS, RECENT_QUERIES, } from './constants'; - import { Analytics, TopQueries, @@ -46,40 +46,41 @@ interface Props { engineBreadcrumb: BreadcrumbTrail; } export const AnalyticsRouter: React.FC = ({ engineBreadcrumb }) => { + const { generateEnginePath } = useValues(EngineLogic); + const ANALYTICS_BREADCRUMB = [...engineBreadcrumb, ANALYTICS_TITLE]; - const engineName = engineBreadcrumb[1]; return ( - + - + - + - + - + - + - + - - + + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx index 93aff04b3f7c0..d8684355c1a81 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx @@ -5,6 +5,7 @@ */ import { setMockValues, setMockActions } from '../../../__mocks__/kea.mock'; +import { mockEngineValues } from '../../__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; @@ -14,16 +15,13 @@ import { EuiCardTo } from '../../../shared/react_router_helpers'; import { DocumentCreationButtons } from './'; describe('DocumentCreationButtons', () => { - const values = { - engineName: 'test-engine', - }; const actions = { openDocumentCreation: jest.fn(), }; beforeEach(() => { jest.clearAllMocks(); - setMockValues(values); + setMockValues(mockEngineValues); setMockActions(actions); }); @@ -57,6 +55,6 @@ describe('DocumentCreationButtons', () => { it('renders the crawler button with a link to the crawler page', () => { const wrapper = shallow(); - expect(wrapper.find(EuiCardTo).prop('to')).toEqual('/engines/test-engine/crawler'); + expect(wrapper.find(EuiCardTo).prop('to')).toEqual('/engines/some-engine/crawler'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx index ce7cae5678338..93c93224b5982 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import { EuiCardTo } from '../../../shared/react_router_helpers'; -import { DOCS_PREFIX, getEngineRoute, ENGINE_CRAWLER_PATH } from '../../routes'; +import { DOCS_PREFIX, ENGINE_CRAWLER_PATH } from '../../routes'; import { EngineLogic } from '../engine'; import { DocumentCreationLogic } from './'; @@ -33,8 +33,8 @@ interface Props { export const DocumentCreationButtons: React.FC = ({ disabled = false }) => { const { openDocumentCreation } = useActions(DocumentCreationLogic); - const { engineName } = useValues(EngineLogic); - const crawlerLink = getEngineRoute(engineName) + ENGINE_CRAWLER_PATH; + const { generateEnginePath } = useValues(EngineLogic); + const crawlerLink = generateEnginePath(ENGINE_CRAWLER_PATH); return ( <> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts index f7476083009df..e33cd9b0e9e71 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts @@ -11,10 +11,7 @@ import { mockFlashMessageHelpers, expectedAsyncError, } from '../../../__mocks__'; - -jest.mock('../engine', () => ({ - EngineLogic: { values: { engineName: 'engine1' } }, -})); +import { mockEngineValues } from '../../__mocks__'; import { DocumentDetailLogic } from './document_detail_logic'; import { InternalSchemaTypes } from '../../../shared/types'; @@ -32,6 +29,7 @@ describe('DocumentDetailLogic', () => { beforeEach(() => { jest.clearAllMocks(); + mockEngineValues.engineName = 'engine1'; }); describe('actions', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts index 62db2bf172354..b8d67ac56b3a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts @@ -7,12 +7,14 @@ import { kea, MakeLogicType } from 'kea'; import { i18n } from '@kbn/i18n'; +import { flashAPIErrors, setQueuedSuccessMessage } from '../../../shared/flash_messages'; +import { KibanaLogic } from '../../../shared/kibana'; import { HttpLogic } from '../../../shared/http'; + +import { ENGINE_DOCUMENTS_PATH } from '../../routes'; import { EngineLogic } from '../engine'; -import { flashAPIErrors, setQueuedSuccessMessage } from '../../../shared/flash_messages'; + import { FieldDetails } from './types'; -import { KibanaLogic } from '../../../shared/kibana'; -import { ENGINE_DOCUMENTS_PATH, getEngineRoute } from '../../routes'; interface DocumentDetailLogicValues { dataLoading: boolean; @@ -27,19 +29,6 @@ interface DocumentDetailLogicActions { type DocumentDetailLogicType = MakeLogicType; -const CONFIRM_DELETE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentDetail.confirmDelete', - { - defaultMessage: 'Are you sure you want to delete this document?', - } -); -const DELETE_SUCCESS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentDetail.deleteSuccess', - { - defaultMessage: 'Successfully marked document for deletion. It will be deleted momentarily.', - } -); - export const DocumentDetailLogic = kea({ path: ['enterprise_search', 'app_search', 'document_detail_logic'], actions: () => ({ @@ -63,7 +52,8 @@ export const DocumentDetailLogic = kea({ }), listeners: ({ actions }) => ({ getDocumentDetails: async ({ documentId }) => { - const { engineName } = EngineLogic.values; + const { engineName, generateEnginePath } = EngineLogic.values; + const { navigateToUrl } = KibanaLogic.values; try { const { http } = HttpLogic.values; @@ -76,20 +66,31 @@ export const DocumentDetailLogic = kea({ // error that will prevent the page from loading, so redirect to the documents page and // show the error flashAPIErrors(e, { isQueued: true }); - const engineRoute = getEngineRoute(engineName); - KibanaLogic.values.navigateToUrl(engineRoute + ENGINE_DOCUMENTS_PATH); + navigateToUrl(generateEnginePath(ENGINE_DOCUMENTS_PATH)); } }, deleteDocument: async ({ documentId }) => { - const { engineName } = EngineLogic.values; + const { engineName, generateEnginePath } = EngineLogic.values; + const { navigateToUrl } = KibanaLogic.values; + + const CONFIRM_DELETE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentDetail.confirmDelete', + { defaultMessage: 'Are you sure you want to delete this document?' } + ); + const DELETE_SUCCESS = i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentDetail.deleteSuccess', + { + defaultMessage: + 'Successfully marked document for deletion. It will be deleted momentarily.', + } + ); if (window.confirm(CONFIRM_DELETE)) { try { const { http } = HttpLogic.values; await http.delete(`/api/app_search/engines/${engineName}/documents/${documentId}`); setQueuedSuccessMessage(DELETE_SUCCESS); - const engineRoute = getEngineRoute(engineName); - KibanaLogic.values.navigateToUrl(engineRoute + ENGINE_DOCUMENTS_PATH); + navigateToUrl(generateEnginePath(ENGINE_DOCUMENTS_PATH)); } catch (e) { flashAPIErrors(e); } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts index 48cbaeef70c1a..32c3382cf187a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -36,6 +36,7 @@ describe('EngineLogic', () => { dataLoading: true, engine: {}, engineName: '', + generateEnginePath: expect.any(Function), isMetaEngine: false, isSampleEngine: false, hasSchemaConflicts: false, @@ -197,6 +198,28 @@ describe('EngineLogic', () => { }); describe('selectors', () => { + describe('generateEnginePath', () => { + it('returns helper function that generates paths with engineName prefilled', () => { + mount({ engineName: 'hello-world' }); + + const generatedPath = EngineLogic.values.generateEnginePath('/engines/:engineName/example'); + expect(generatedPath).toEqual('/engines/hello-world/example'); + }); + + it('allows overriding engineName and filling other params', () => { + mount({ engineName: 'lorem-ipsum' }); + + const generatedPath = EngineLogic.values.generateEnginePath( + '/engines/:engineName/foo/:bar', + { + engineName: 'dolor-sit', + bar: 'baz', + } + ); + expect(generatedPath).toEqual('/engines/dolor-sit/foo/baz'); + }); + }); + describe('isSampleEngine', () => { it('should be set based on engine.sample', () => { const mockSampleEngine = { ...mockEngineData, sample: true }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts index 9f3fe721b74de..04d06b596080a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts @@ -5,6 +5,7 @@ */ import { kea, MakeLogicType } from 'kea'; +import { generatePath } from 'react-router-dom'; import { HttpLogic } from '../../../shared/http'; @@ -15,6 +16,7 @@ interface EngineValues { dataLoading: boolean; engine: Partial; engineName: string; + generateEnginePath: Function; isMetaEngine: boolean; isSampleEngine: boolean; hasSchemaConflicts: boolean; @@ -76,6 +78,15 @@ export const EngineLogic = kea>({ ], }, selectors: ({ selectors }) => ({ + generateEnginePath: [ + () => [selectors.engineName], + (engineName) => { + const generateEnginePath = (path: string, pathParams: object = {}) => { + return generatePath(path, { engineName, ...pathParams }); + }; + return generateEnginePath; + }, + ], isMetaEngine: [() => [selectors.engine], (engine) => engine?.type === 'meta'], isSampleEngine: [() => [selectors.engine], (engine) => !!engine?.sample], hasSchemaConflicts: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx index 95c9beb9b866e..f4ef2f5963c32 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx @@ -5,15 +5,16 @@ */ import { setMockValues, rerender } from '../../../__mocks__'; +import { mockEngineValues } from '../../__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; import { EuiBadge, EuiIcon } from '@elastic/eui'; -import { EngineNav } from './'; +import { EngineNav } from './engine_nav'; describe('EngineNav', () => { - const values = { myRole: {}, engineName: 'some-engine', dataLoading: false, engine: {} }; + const values = { ...mockEngineValues, myRole: {}, dataLoading: false }; beforeEach(() => { setMockValues(values); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index 40ae2cef0acb8..fd30e04d34932 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n'; import { SideNavLink, SideNavItem } from '../../../shared/layout'; import { AppLogic } from '../../app_logic'; import { - getEngineRoute, + ENGINE_PATH, ENGINE_ANALYTICS_PATH, ENGINE_DOCUMENTS_PATH, ENGINE_SCHEMA_PATH, @@ -64,6 +64,7 @@ export const EngineNav: React.FC = () => { const { engineName, + generateEnginePath, dataLoading, isSampleEngine, isMetaEngine, @@ -75,7 +76,6 @@ export const EngineNav: React.FC = () => { if (dataLoading) return null; if (!engineName) return null; - const engineRoute = getEngineRoute(engineName); const { invalidBoosts, unsearchedUnconfirmedFields } = engine as Required; return ( @@ -99,12 +99,12 @@ export const EngineNav: React.FC = () => { )} - + {OVERVIEW_TITLE} {canViewEngineAnalytics && ( @@ -113,7 +113,7 @@ export const EngineNav: React.FC = () => { )} {canViewEngineDocuments && ( @@ -123,7 +123,7 @@ export const EngineNav: React.FC = () => { {canViewEngineSchema && ( @@ -158,7 +158,7 @@ export const EngineNav: React.FC = () => { {canViewEngineCrawler && !isMetaEngine && ( {CRAWLER_TITLE} @@ -167,7 +167,7 @@ export const EngineNav: React.FC = () => { {canViewMetaEngineSourceEngines && isMetaEngine && ( {ENGINES_TITLE} @@ -176,7 +176,7 @@ export const EngineNav: React.FC = () => { {canManageEngineRelevanceTuning && ( @@ -211,7 +211,7 @@ export const EngineNav: React.FC = () => { {canManageEngineSynonyms && ( {SYNONYMS_TITLE} @@ -220,7 +220,7 @@ export const EngineNav: React.FC = () => { {canManageEngineCurations && ( {CURATIONS_TITLE} @@ -229,7 +229,7 @@ export const EngineNav: React.FC = () => { {canManageEngineResultSettings && ( {RESULT_SETTINGS_TITLE} @@ -238,7 +238,7 @@ export const EngineNav: React.FC = () => { {canManageEngineSearchUi && ( {SEARCH_UI_TITLE} @@ -247,7 +247,7 @@ export const EngineNav: React.FC = () => { {canViewEngineApiLogs && ( {API_LOGS_TITLE} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx index 362454c31f0d9..aa8b406cf7774 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx @@ -7,6 +7,7 @@ import '../../../__mocks__/react_router_history.mock'; import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock'; import { mockFlashMessageHelpers, setMockValues, setMockActions } from '../../../__mocks__'; +import { mockEngineValues } from '../../__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; @@ -16,14 +17,14 @@ import { Loading } from '../../../shared/loading'; import { EngineOverview } from '../engine_overview'; import { AnalyticsRouter } from '../analytics'; -import { EngineRouter } from './'; +import { EngineRouter } from './engine_router'; describe('EngineRouter', () => { const values = { + ...mockEngineValues, dataLoading: false, engineNotFound: false, myRole: {}, - engineName: 'some-engine', }; const actions = { setEngineName: jest.fn(), initializeEngine: jest.fn(), clearEngine: jest.fn() }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx index 47fe302ac7014..fd21507a427d5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx @@ -17,7 +17,6 @@ import { AppLogic } from '../../app_logic'; // TODO: Uncomment and add more routes as we migrate them import { ENGINES_PATH, - ENGINE_PATH, ENGINE_ANALYTICS_PATH, ENGINE_DOCUMENTS_PATH, ENGINE_DOCUMENT_DETAIL_PATH, @@ -86,14 +85,14 @@ export const EngineRouter: React.FC = () => { return ( {canViewEngineAnalytics && ( - + )} - + - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx index fb34682e3c7ec..9da63ca639bbf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx @@ -5,6 +5,7 @@ */ import { setMockValues } from '../../../../__mocks__/kea.mock'; +import { mockEngineValues } from '../../../__mocks__'; import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; @@ -18,9 +19,7 @@ describe('RecentApiLogs', () => { beforeAll(() => { jest.clearAllMocks(); - setMockValues({ - engineName: 'some-engine', - }); + setMockValues(mockEngineValues); wrapper = shallow(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx index 3f42419252d28..19c931cefc1e3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx @@ -16,16 +16,14 @@ import { } from '@elastic/eui'; import { EuiButtonTo } from '../../../../shared/react_router_helpers'; +import { ENGINE_API_LOGS_PATH } from '../../../routes'; +import { EngineLogic } from '../../engine'; -import { ENGINE_API_LOGS_PATH, getEngineRoute } from '../../../routes'; import { RECENT_API_EVENTS } from '../../api_logs/constants'; import { VIEW_API_LOGS } from '../constants'; -import { EngineLogic } from '../../engine'; - export const RecentApiLogs: React.FC = () => { - const { engineName } = useValues(EngineLogic); - const engineRoute = getEngineRoute(engineName); + const { generateEnginePath } = useValues(EngineLogic); return ( @@ -36,7 +34,7 @@ export const RecentApiLogs: React.FC = () => { - + {VIEW_API_LOGS} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx index b1350b7e102e3..98718dea7130f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx @@ -5,6 +5,7 @@ */ import { setMockValues } from '../../../../__mocks__/kea.mock'; +import { mockEngineValues } from '../../../__mocks__'; import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; @@ -20,7 +21,7 @@ describe('TotalCharts', () => { beforeAll(() => { jest.clearAllMocks(); setMockValues({ - engineName: 'some-engine', + ...mockEngineValues, startDate: '1970-01-01', queriesPerDay: [0, 1, 2, 3, 5, 10, 50], operationsPerDay: [0, 0, 0, 0, 0, 0, 0], diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx index 4ef4e08dee761..02453cc8a150f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx @@ -19,20 +19,16 @@ import { } from '@elastic/eui'; import { EuiButtonTo } from '../../../../shared/react_router_helpers'; +import { ENGINE_ANALYTICS_PATH, ENGINE_API_LOGS_PATH } from '../../../routes'; +import { EngineLogic } from '../../engine'; -import { ENGINE_ANALYTICS_PATH, ENGINE_API_LOGS_PATH, getEngineRoute } from '../../../routes'; import { TOTAL_QUERIES, TOTAL_API_OPERATIONS } from '../../analytics/constants'; import { VIEW_ANALYTICS, VIEW_API_LOGS, LAST_7_DAYS } from '../constants'; - import { AnalyticsChart, convertToChartData } from '../../analytics'; - -import { EngineLogic } from '../../engine'; import { EngineOverviewLogic } from '../'; export const TotalCharts: React.FC = () => { - const { engineName } = useValues(EngineLogic); - const engineRoute = getEngineRoute(engineName); - + const { generateEnginePath } = useValues(EngineLogic); const { startDate, queriesPerDay, operationsPerDay } = useValues(EngineOverviewLogic); return ( @@ -49,7 +45,7 @@ export const TotalCharts: React.FC = () => { - + {VIEW_ANALYTICS} @@ -78,7 +74,7 @@ export const TotalCharts: React.FC = () => { - + {VIEW_API_LOGS} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx index 1dde4db15a425..a0f150ca4ec42 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../__mocks__/kea.mock'; import '../../../__mocks__/enterprise_search_url.mock'; -import { mockTelemetryActions, mountWithIntl } from '../../../__mocks__/'; +import { mockTelemetryActions, mountWithIntl } from '../../../__mocks__'; import React from 'react'; import { EuiBasicTable, EuiPagination, EuiButtonEmpty } from '@elastic/eui'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx index e8944c37efa47..a9455b4a2306a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; +import { generatePath } from 'react-router-dom'; import { useActions } from 'kea'; import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import { FormattedMessage, FormattedDate, FormattedNumber } from '@kbn/i18n/react'; @@ -12,7 +13,7 @@ import { i18n } from '@kbn/i18n'; import { TelemetryLogic } from '../../../shared/telemetry'; import { EuiLinkTo } from '../../../shared/react_router_helpers'; -import { getEngineRoute } from '../../routes'; +import { ENGINE_PATH } from '../../routes'; import { ENGINES_PAGE_SIZE } from '../../../../../common/constants'; import { UNIVERSAL_LANGUAGE } from '../../constants'; @@ -39,8 +40,8 @@ export const EnginesTable: React.FC = ({ }) => { const { sendAppSearchTelemetry } = useActions(TelemetryLogic); - const engineLinkProps = (name: string) => ({ - to: getEngineRoute(name), + const engineLinkProps = (engineName: string) => ({ + to: generatePath(ENGINE_PATH, { engineName }), onClick: () => sendAppSearchTelemetry({ action: 'clicked', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx index f25eb2a4ba09e..a3935bb782f90 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx @@ -5,6 +5,7 @@ */ import React, { useState, useMemo } from 'react'; +import { generatePath } from 'react-router-dom'; import classNames from 'classnames'; import './result.scss'; @@ -12,12 +13,13 @@ import './result.scss'; import { EuiPanel, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; +import { ENGINE_DOCUMENT_DETAIL_PATH } from '../../routes'; + +import { Schema } from '../../../shared/types'; import { FieldValue, Result as ResultType } from './types'; import { ResultField } from './result_field'; import { ResultHeader } from './result_header'; -import { getDocumentDetailRoute } from '../../routes'; -import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; -import { Schema } from '../../../shared/types'; interface Props { result: ResultType; @@ -50,7 +52,10 @@ export const Result: React.FC = ({ if (schemaForTypeHighlights) return schemaForTypeHighlights[fieldName]; }; - const documentLink = getDocumentDetailRoute(resultMeta.engine, resultMeta.id); + const documentLink = generatePath(ENGINE_DOCUMENT_DETAIL_PATH, { + engineName: resultMeta.engine, + documentId: resultMeta.id, + }); const conditionallyLinkedArticle = (children: React.ReactNode) => { return shouldLinkToDetailPage ? ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts index 0f3d34cfa6337..41e9bfa19e0f0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { generatePath } from 'react-router-dom'; - import { CURRENT_MAJOR_VERSION } from '../../../common/version'; export const DOCS_PREFIX = `https://www.elastic.co/guide/en/app-search/${CURRENT_MAJOR_VERSION}`; @@ -20,11 +18,10 @@ export const ROLE_MAPPINGS_PATH = '#/role-mappings'; // This page seems to 404 i export const ENGINES_PATH = '/engines'; export const CREATE_ENGINES_PATH = `${ENGINES_PATH}/new`; -export const ENGINE_PATH = '/engines/:engineName'; -export const SAMPLE_ENGINE_PATH = '/engines/national-parks-demo'; -export const getEngineRoute = (engineName: string) => generatePath(ENGINE_PATH, { engineName }); +export const ENGINE_PATH = `${ENGINES_PATH}/:engineName`; +export const SAMPLE_ENGINE_PATH = `${ENGINES_PATH}/national-parks-demo`; -export const ENGINE_ANALYTICS_PATH = '/analytics'; +export const ENGINE_ANALYTICS_PATH = `${ENGINE_PATH}/analytics`; export const ENGINE_ANALYTICS_TOP_QUERIES_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries`; export const ENGINE_ANALYTICS_TOP_QUERIES_NO_CLICKS_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries_no_clicks`; export const ENGINE_ANALYTICS_TOP_QUERIES_NO_RESULTS_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries_no_results`; @@ -33,25 +30,22 @@ export const ENGINE_ANALYTICS_RECENT_QUERIES_PATH = `${ENGINE_ANALYTICS_PATH}/re export const ENGINE_ANALYTICS_QUERY_DETAILS_PATH = `${ENGINE_ANALYTICS_PATH}/query_detail`; export const ENGINE_ANALYTICS_QUERY_DETAIL_PATH = `${ENGINE_ANALYTICS_QUERY_DETAILS_PATH}/:query`; -export const ENGINE_DOCUMENTS_PATH = '/documents'; +export const ENGINE_DOCUMENTS_PATH = `${ENGINE_PATH}/documents`; export const ENGINE_DOCUMENT_DETAIL_PATH = `${ENGINE_DOCUMENTS_PATH}/:documentId`; -export const getDocumentDetailRoute = (engineName: string, documentId: string) => { - return generatePath(ENGINE_PATH + ENGINE_DOCUMENT_DETAIL_PATH, { engineName, documentId }); -}; -export const ENGINE_SCHEMA_PATH = '/schema/edit'; -export const ENGINE_REINDEX_JOB_PATH = '/reindex-job/:activeReindexJobId'; +export const ENGINE_SCHEMA_PATH = `${ENGINE_PATH}/schema/edit`; +export const ENGINE_REINDEX_JOB_PATH = `${ENGINE_PATH}/reindex-job/:activeReindexJobId`; -export const ENGINE_CRAWLER_PATH = '/crawler'; +export const ENGINE_CRAWLER_PATH = `${ENGINE_PATH}/crawler`; // TODO: Crawler sub-pages -export const META_ENGINE_SOURCE_ENGINES_PATH = '/engines'; +export const META_ENGINE_SOURCE_ENGINES_PATH = `${ENGINE_PATH}/engines`; -export const ENGINE_RELEVANCE_TUNING_PATH = '/search-settings'; -export const ENGINE_SYNONYMS_PATH = '/synonyms'; -export const ENGINE_CURATIONS_PATH = '/curations'; +export const ENGINE_RELEVANCE_TUNING_PATH = `${ENGINE_PATH}/search-settings`; +export const ENGINE_SYNONYMS_PATH = `${ENGINE_PATH}/synonyms`; +export const ENGINE_CURATIONS_PATH = `${ENGINE_PATH}/curations`; // TODO: Curations sub-pages -export const ENGINE_RESULT_SETTINGS_PATH = '/result-settings'; +export const ENGINE_RESULT_SETTINGS_PATH = `${ENGINE_PATH}/result-settings`; -export const ENGINE_SEARCH_UI_PATH = '/reference_application/new'; -export const ENGINE_API_LOGS_PATH = '/api-logs'; +export const ENGINE_SEARCH_UI_PATH = `${ENGINE_PATH}/reference_application/new`; +export const ENGINE_API_LOGS_PATH = `${ENGINE_PATH}/api-logs`; From 17043d4f9d699855852841449dfe2f6c4ad377e4 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 21 Jan 2021 11:25:02 -0800 Subject: [PATCH 69/83] Use doc link service in Stack Monitoring (#88920) --- src/core/public/doc_links/doc_links_service.ts | 5 +++++ .../public/alerts/ccr_read_exceptions_alert/index.tsx | 2 +- .../public/alerts/cpu_usage_alert/cpu_usage_alert.tsx | 2 +- .../monitoring/public/alerts/disk_usage_alert/index.tsx | 2 +- .../monitoring/public/alerts/legacy_alert/legacy_alert.tsx | 2 +- .../monitoring/public/alerts/memory_usage_alert/index.tsx | 2 +- .../missing_monitoring_data_alert.tsx | 2 +- .../public/alerts/thread_pool_rejections_alert/index.tsx | 2 +- 8 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 1a69c7db35a73..b82254e5a1416 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -182,7 +182,12 @@ export class DocLinksService { guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/maps.html`, }, monitoring: { + alertsCluster: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/cluster-alerts.html`, alertsKibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html`, + alertsKibanaCpuThreshold: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-cpu-threshold`, + alertsKibanaDiskThreshold: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-disk-usage-threshold`, + alertsKibanaJvmThreshold: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-jvm-memory-threshold`, + alertsKibanaMissingData: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-missing-monitoring-data`, monitorElasticsearch: `${ELASTICSEARCH_DOCS}configuring-metricbeat.html`, monitorKibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/monitoring-metricbeat.html`, }, diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx index 6d7751d91b761..e656c0ab253e0 100644 --- a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx @@ -37,7 +37,7 @@ export function createCCRReadExceptionsAlertType(): AlertTypeModel ( diff --git a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx index d2cec006b1b1d..9b207457683f6 100644 --- a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx @@ -16,7 +16,7 @@ export function createCpuUsageAlertType(): AlertTypeModel ( diff --git a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx index bea399ee89f6a..aeb9bab2aae9a 100644 --- a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx @@ -18,7 +18,7 @@ export function createDiskUsageAlertType(): AlertTypeModel ( diff --git a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx index d50e9c3a5c282..4a3532ad61240 100644 --- a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx @@ -18,7 +18,7 @@ export function createLegacyAlertTypes(): AlertTypeModel[] { description: LEGACY_ALERT_DETAILS[legacyAlert].description, iconClass: 'bell', documentationUrl(docLinks) { - return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/cluster-alerts.html`; + return `${docLinks.links.monitoring.alertsCluster}`; }, alertParamsExpression: () => ( diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx index 0428e4e7c733e..b484cd9a975fd 100644 --- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx @@ -18,7 +18,7 @@ export function createMemoryUsageAlertType(): AlertTypeModel ( diff --git a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx index fdb89033c4e2c..18a4990eeaaa7 100644 --- a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx @@ -16,7 +16,7 @@ export function createMissingMonitoringDataAlertType(): AlertTypeModel { description: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].description, iconClass: 'bell', documentationUrl(docLinks) { - return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-missing-monitoring-data`; + return `${docLinks.links.monitoring.alertsKibanaMissingData}`; }, alertParamsExpression: (props: any) => ( ( <> From 76b23f17e2035185aa7bd213af2767b564302a1d Mon Sep 17 00:00:00 2001 From: Sandra Gonzales Date: Thu, 21 Jan 2021 14:32:35 -0500 Subject: [PATCH 70/83] add custom metrics to node tooltip (#88545) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../conditional_tooltip.test.tsx.snap | 42 +++++++++- .../waffle/conditional_tooltip.test.tsx | 82 ++++++++++++++++++- .../components/waffle/conditional_tooltip.tsx | 32 ++++++-- 3 files changed, 143 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap index b8cdc0acac1dc..a5d97813e4b14 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap @@ -21,9 +21,10 @@ exports[`ConditionalToolTip should just work 1`] = ` host-01
CPU usage @@ -35,9 +36,10 @@ exports[`ConditionalToolTip should just work 1`] = ` Memory usage @@ -49,9 +51,10 @@ exports[`ConditionalToolTip should just work 1`] = ` Outbound traffic @@ -63,9 +66,10 @@ exports[`ConditionalToolTip should just work 1`] = ` Inbound traffic @@ -76,5 +80,35 @@ exports[`ConditionalToolTip should just work 1`] = ` 8Mbit/s + + + My Custom Label + + + 34.1% + + + + + Avg of host.network.out.packets + + + 4,392.2 + + `; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx index e01ca3ab6e844..fbca85e2d4496 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx @@ -22,7 +22,12 @@ jest.mock('../../../../../containers/source', () => ({ jest.mock('../../hooks/use_snaphot'); import { useSnapshot } from '../../hooks/use_snaphot'; +jest.mock('../../hooks/use_waffle_options'); +import { useWaffleOptionsContext } from '../../hooks/use_waffle_options'; const mockedUseSnapshot = useSnapshot as jest.Mock>; +const mockedUseWaffleOptionsContext = useWaffleOptionsContext as jest.Mock< + ReturnType +>; const NODE: InfraWaffleMapNode = { pathId: 'host-01', @@ -50,6 +55,7 @@ const ChildComponent = () =>
child
; describe('ConditionalToolTip', () => { afterEach(() => { mockedUseSnapshot.mockReset(); + mockedUseWaffleOptionsContext.mockReset(); }); function createWrapper(currentTime: number = Date.now(), hidden: boolean = false) { @@ -77,6 +83,7 @@ describe('ConditionalToolTip', () => { interval: '', reload: jest.fn(() => Promise.resolve()), }); + mockedUseWaffleOptionsContext.mockReturnValue(mockedUseWaffleOptionsContexReturnValue); const currentTime = Date.now(); const wrapper = createWrapper(currentTime, true); expect(wrapper.find(ChildComponent).exists()).toBeTruthy(); @@ -95,6 +102,18 @@ describe('ConditionalToolTip', () => { { name: 'memory', value: 0.8, avg: 0.8, max: 1 }, { name: 'tx', value: 1000000, avg: 1000000, max: 1000000 }, { name: 'rx', value: 1000000, avg: 1000000, max: 1000000 }, + { + name: 'cedd6ca0-5775-11eb-a86f-adb714b6c486', + max: 0.34164999922116596, + value: 0.34140000740687054, + avg: 0.20920833365784752, + }, + { + name: 'e12dd700-5775-11eb-a86f-adb714b6c486', + max: 4703.166666666667, + value: 4392.166666666667, + avg: 3704.6666666666674, + }, ], }, ], @@ -103,6 +122,7 @@ describe('ConditionalToolTip', () => { interval: '60s', reload: reloadMock, }); + mockedUseWaffleOptionsContext.mockReturnValue(mockedUseWaffleOptionsContexReturnValue); const currentTime = Date.now(); const wrapper = createWrapper(currentTime, false); expect(wrapper.find(ChildComponent).exists()).toBeTruthy(); @@ -114,7 +134,25 @@ describe('ConditionalToolTip', () => { }, }, }); - const expectedMetrics = [{ type: 'cpu' }, { type: 'memory' }, { type: 'tx' }, { type: 'rx' }]; + const expectedMetrics = [ + { type: 'cpu' }, + { type: 'memory' }, + { type: 'tx' }, + { type: 'rx' }, + { + aggregation: 'avg', + field: 'host.cpu.pct', + id: 'cedd6ca0-5775-11eb-a86f-adb714b6c486', + label: 'My Custom Label', + type: 'custom', + }, + { + aggregation: 'avg', + field: 'host.network.out.packets', + id: 'e12dd700-5775-11eb-a86f-adb714b6c486', + type: 'custom', + }, + ]; expect(mockedUseSnapshot).toBeCalledWith( expectedQuery, expectedMetrics, @@ -143,6 +181,7 @@ describe('ConditionalToolTip', () => { interval: '', reload: reloadMock, }); + mockedUseWaffleOptionsContext.mockReturnValue(mockedUseWaffleOptionsContexReturnValue); const currentTime = Date.now(); const wrapper = createWrapper(currentTime, false); expect(wrapper.find(ChildComponent).exists()).toBeTruthy(); @@ -154,3 +193,44 @@ describe('ConditionalToolTip', () => { expect(reloadMock).not.toHaveBeenCalled(); }); }); + +const mockedUseWaffleOptionsContexReturnValue: ReturnType = { + changeMetric: jest.fn(() => {}), + changeGroupBy: jest.fn(() => {}), + changeNodeType: jest.fn(() => {}), + changeView: jest.fn(() => {}), + changeCustomOptions: jest.fn(() => {}), + changeAutoBounds: jest.fn(() => {}), + changeBoundsOverride: jest.fn(() => {}), + changeAccount: jest.fn(() => {}), + changeRegion: jest.fn(() => {}), + changeCustomMetrics: jest.fn(() => {}), + changeLegend: jest.fn(() => {}), + changeSort: jest.fn(() => {}), + setWaffleOptionsState: jest.fn(() => {}), + boundsOverride: { max: 1, min: 0 }, + autoBounds: true, + accountId: '', + region: '', + sort: { by: 'name', direction: 'desc' }, + groupBy: [], + nodeType: 'host', + customOptions: [], + view: 'map', + metric: { type: 'cpu' }, + customMetrics: [ + { + aggregation: 'avg', + field: 'host.cpu.pct', + id: 'cedd6ca0-5775-11eb-a86f-adb714b6c486', + label: 'My Custom Label', + type: 'custom', + }, + { + aggregation: 'avg', + field: 'host.network.out.packets', + id: 'e12dd700-5775-11eb-a86f-adb714b6c486', + type: 'custom', + }, + ], +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx index 8082752a88b7f..7ec1ae905a640 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx @@ -6,6 +6,8 @@ import React, { useCallback, useState, useEffect } from 'react'; import { EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { first } from 'lodash'; +import { getCustomMetricLabel } from '../../../../../../common/formatters/get_custom_metric_label'; +import { SnapshotCustomMetricInput } from '../../../../../../common/http_api'; import { withTheme, EuiTheme } from '../../../../../../../observability/public'; import { useSourceContext } from '../../../../../containers/source'; import { findInventoryModel } from '../../../../../../common/inventory_models'; @@ -18,6 +20,8 @@ import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/li import { useSnapshot } from '../../hooks/use_snaphot'; import { createInventoryMetricFormatter } from '../../lib/create_inventory_metric_formatter'; import { SNAPSHOT_METRIC_TRANSLATIONS } from '../../../../../../common/inventory_models/intl_strings'; +import { useWaffleOptionsContext } from '../../hooks/use_waffle_options'; +import { createFormatterForMetric } from '../../../metrics_explorer/components/helpers/create_formatter_for_metric'; export interface Props { currentTime: number; @@ -35,9 +39,15 @@ export const ConditionalToolTip = withTheme( const { sourceId } = useSourceContext(); const [timer, setTimer] = useState | null>(null); const model = findInventoryModel(nodeType); - const requestMetrics = model.tooltipMetrics.map((type) => ({ type })) as Array<{ - type: SnapshotMetricType; - }>; + const { customMetrics } = useWaffleOptionsContext(); + const requestMetrics = model.tooltipMetrics + .map((type) => ({ type })) + .concat(customMetrics) as Array< + | { + type: SnapshotMetricType; + } + | SnapshotCustomMetricInput + >; const query = JSON.stringify({ bool: { filter: { @@ -45,7 +55,6 @@ export const ConditionalToolTip = withTheme( }, }, }); - const { nodes, reload } = useSnapshot( query, requestMetrics, @@ -74,7 +83,6 @@ export const ConditionalToolTip = withTheme( if (hidden) { return children; } - const dataNode = first(nodes); const metrics = (dataNode && dataNode.metrics) || []; const content = ( @@ -91,10 +99,18 @@ export const ConditionalToolTip = withTheme( {metrics.map((metric) => { const metricName = SnapshotMetricTypeRT.is(metric.name) ? metric.name : 'custom'; const name = SNAPSHOT_METRIC_TRANSLATIONS[metricName] || metricName; - const formatter = createInventoryMetricFormatter({ type: metricName }); + // if custom metric, find field and label from waffleOptionsContext result + // because useSnapshot does not return it + const customMetric = + name === 'custom' ? customMetrics.find((item) => item.id === metric.name) : null; + const formatter = customMetric + ? createFormatterForMetric(customMetric) + : createInventoryMetricFormatter({ type: metricName }); return ( - - {name} + + + {customMetric ? getCustomMetricLabel(customMetric) : name} + {(metric.value && formatter(metric.value)) || '-'} From ae0bd2fbba07901445260a0cbd264da6017eb152 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Thu, 21 Jan 2021 14:10:19 -0600 Subject: [PATCH 71/83] Add runtime fields to index patterns and searchsource (#88542) * Add runtime fields to index patterns and searchsource --- ...ata-public.indexpattern.addruntimefield.md | 25 +++++ ...ublic.indexpattern.getassavedobjectbody.md | 2 + ...a-public.indexpattern.getcomputedfields.md | 2 + ...plugin-plugins-data-public.indexpattern.md | 2 + ...-public.indexpattern.removeruntimefield.md | 24 +++++ ...gins-data-public.indexpatternattributes.md | 1 + ....indexpatternattributes.runtimefieldmap.md | 11 +++ ...-data-public.indexpatternfield.ismapped.md | 13 +++ ...n-plugins-data-public.indexpatternfield.md | 2 + ...a-public.indexpatternfield.runtimefield.md | 13 +++ ...in-plugins-data-public.indexpatternspec.md | 1 + ...public.indexpatternspec.runtimefieldmap.md | 11 +++ ...ata-server.indexpattern.addruntimefield.md | 25 +++++ ...erver.indexpattern.getassavedobjectbody.md | 2 + ...a-server.indexpattern.getcomputedfields.md | 2 + ...plugin-plugins-data-server.indexpattern.md | 2 + ...-server.indexpattern.removeruntimefield.md | 24 +++++ ...gins-data-server.indexpatternattributes.md | 1 + ....indexpatternattributes.runtimefieldmap.md | 11 +++ .../index_pattern_field.test.ts.snap | 7 ++ .../fields/index_pattern_field.test.ts | 8 +- .../fields/index_pattern_field.ts | 20 +++- .../__snapshots__/index_pattern.test.ts.snap | 93 +++++++++++++++++++ .../__snapshots__/index_patterns.test.ts.snap | 1 + .../fixtures/logstash_fields.js | 1 + .../index_patterns/index_pattern.test.ts | 86 ++++++++++++++++- .../index_patterns/index_pattern.ts | 57 +++++++++++- .../index_patterns/index_patterns.ts | 29 +++++- .../data/common/index_patterns/types.ts | 12 +++ .../search_source/search_source.test.ts | 22 ++++- .../search/search_source/search_source.ts | 15 ++- src/plugins/data/public/public.api.md | 17 +++- src/plugins/data/server/server.api.md | 13 ++- .../helpers/get_sharing_data.test.ts | 1 + .../test/functional/apps/maps/mvt_scaling.js | 2 +- .../functional/apps/maps/mvt_super_fine.js | 2 +- 36 files changed, 542 insertions(+), 18 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addruntimefield.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.runtimefieldmap.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.ismapped.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.runtimefield.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.runtimefieldmap.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addruntimefield.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.runtimefieldmap.md diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addruntimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addruntimefield.md new file mode 100644 index 0000000000000..5640395139ba6 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addruntimefield.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [addRuntimeField](./kibana-plugin-plugins-data-public.indexpattern.addruntimefield.md) + +## IndexPattern.addRuntimeField() method + +Add a runtime field - Appended to existing mapped field or a new field is created as appropriate + +Signature: + +```typescript +addRuntimeField(name: string, runtimeField: RuntimeField): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | +| runtimeField | RuntimeField | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md index b318427012c0a..48d94b84497bd 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md @@ -20,6 +20,7 @@ getAsSavedObjectBody(): { type: string | undefined; typeMeta: string | undefined; allowNoIndex: true | undefined; + runtimeFieldMap: string | undefined; }; ``` Returns: @@ -35,5 +36,6 @@ getAsSavedObjectBody(): { type: string | undefined; typeMeta: string | undefined; allowNoIndex: true | undefined; + runtimeFieldMap: string | undefined; }` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getcomputedfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getcomputedfields.md index 84aeb9ffeb21a..37d31a35167df 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getcomputedfields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getcomputedfields.md @@ -14,6 +14,7 @@ getComputedFields(): { field: any; format: string; }[]; + runtimeFields: Record; }; ``` Returns: @@ -25,5 +26,6 @@ getComputedFields(): { field: any; format: string; }[]; + runtimeFields: Record; }` 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 872e23e450f88..53d173d39f50d 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 @@ -45,6 +45,7 @@ export declare class IndexPattern implements IIndexPattern | Method | Modifiers | Description | | --- | --- | --- | +| [addRuntimeField(name, runtimeField)](./kibana-plugin-plugins-data-public.indexpattern.addruntimefield.md) | | Add a runtime field - Appended to existing mapped field or a new field is created as appropriate | | [addScriptedField(name, script, fieldType)](./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 | @@ -58,6 +59,7 @@ export declare class IndexPattern implements IIndexPattern | [getTimeField()](./kibana-plugin-plugins-data-public.indexpattern.gettimefield.md) | | | | [isTimeBased()](./kibana-plugin-plugins-data-public.indexpattern.istimebased.md) | | | | [isTimeNanosBased()](./kibana-plugin-plugins-data-public.indexpattern.istimenanosbased.md) | | | +| [removeRuntimeField(name)](./kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md) | | Remove a runtime field - removed from mapped field or removed unmapped field as appropriate | | [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | | [setFieldAttrs(fieldName, attrName, value)](./kibana-plugin-plugins-data-public.indexpattern.setfieldattrs.md) | | | | [setFieldCount(fieldName, count)](./kibana-plugin-plugins-data-public.indexpattern.setfieldcount.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md new file mode 100644 index 0000000000000..7a5228fece782 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [removeRuntimeField](./kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md) + +## IndexPattern.removeRuntimeField() method + +Remove a runtime field - removed from mapped field or removed unmapped field as appropriate + +Signature: + +```typescript +removeRuntimeField(name: string): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`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 297bfa855f0eb..41a4d3c55694b 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 @@ -21,6 +21,7 @@ export interface IndexPatternAttributes | [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpatternattributes.fieldformatmap.md) | string | | | [fields](./kibana-plugin-plugins-data-public.indexpatternattributes.fields.md) | string | | | [intervalName](./kibana-plugin-plugins-data-public.indexpatternattributes.intervalname.md) | string | | +| [runtimeFieldMap](./kibana-plugin-plugins-data-public.indexpatternattributes.runtimefieldmap.md) | string | | | [sourceFilters](./kibana-plugin-plugins-data-public.indexpatternattributes.sourcefilters.md) | string | | | [timeFieldName](./kibana-plugin-plugins-data-public.indexpatternattributes.timefieldname.md) | string | | | [title](./kibana-plugin-plugins-data-public.indexpatternattributes.title.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.runtimefieldmap.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.runtimefieldmap.md new file mode 100644 index 0000000000000..0df7a9841e41f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.runtimefieldmap.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) > [runtimeFieldMap](./kibana-plugin-plugins-data-public.indexpatternattributes.runtimefieldmap.md) + +## IndexPatternAttributes.runtimeFieldMap property + +Signature: + +```typescript +runtimeFieldMap?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.ismapped.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.ismapped.md new file mode 100644 index 0000000000000..653a1f2b39c29 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.ismapped.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [isMapped](./kibana-plugin-plugins-data-public.indexpatternfield.ismapped.md) + +## IndexPatternField.isMapped property + +Is the field part of the index mapping? + +Signature: + +```typescript +get isMapped(): boolean | undefined; +``` 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 c8118770ed394..05c807b1cd845 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 @@ -27,9 +27,11 @@ export declare class IndexPatternField implements IFieldType | [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 | | +| [isMapped](./kibana-plugin-plugins-data-public.indexpatternfield.ismapped.md) | | boolean | undefined | Is the field part of the index mapping? | | [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 | | +| [runtimeField](./kibana-plugin-plugins-data-public.indexpatternfield.runtimefield.md) | | RuntimeField | 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 | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.runtimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.runtimefield.md new file mode 100644 index 0000000000000..ad3b81eb23edc --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.runtimefield.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [runtimeField](./kibana-plugin-plugins-data-public.indexpatternfield.runtimefield.md) + +## IndexPatternField.runtimeField property + +Signature: + +```typescript +get runtimeField(): RuntimeField | undefined; + +set runtimeField(runtimeField: RuntimeField | undefined); +``` 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 index c0fa165cfb115..ae514e3fc6a8a 100644 --- 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 @@ -22,6 +22,7 @@ export interface IndexPatternSpec | [fields](./kibana-plugin-plugins-data-public.indexpatternspec.fields.md) | IndexPatternFieldMap | | | [id](./kibana-plugin-plugins-data-public.indexpatternspec.id.md) | string | saved object id | | [intervalName](./kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md) | string | | +| [runtimeFieldMap](./kibana-plugin-plugins-data-public.indexpatternspec.runtimefieldmap.md) | Record<string, RuntimeField> | | | [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 | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.runtimefieldmap.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.runtimefieldmap.md new file mode 100644 index 0000000000000..e208760ff188f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.runtimefieldmap.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) > [runtimeFieldMap](./kibana-plugin-plugins-data-public.indexpatternspec.runtimefieldmap.md) + +## IndexPatternSpec.runtimeFieldMap property + +Signature: + +```typescript +runtimeFieldMap?: Record; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addruntimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addruntimefield.md new file mode 100644 index 0000000000000..ebd7f46d3598e --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addruntimefield.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [addRuntimeField](./kibana-plugin-plugins-data-server.indexpattern.addruntimefield.md) + +## IndexPattern.addRuntimeField() method + +Add a runtime field - Appended to existing mapped field or a new field is created as appropriate + +Signature: + +```typescript +addRuntimeField(name: string, runtimeField: RuntimeField): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | +| runtimeField | RuntimeField | | + +Returns: + +`void` + 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 index 7d70af4b535fe..668d563ff04c0 100644 --- 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 @@ -20,6 +20,7 @@ getAsSavedObjectBody(): { type: string | undefined; typeMeta: string | undefined; allowNoIndex: true | undefined; + runtimeFieldMap: string | undefined; }; ``` Returns: @@ -35,5 +36,6 @@ getAsSavedObjectBody(): { type: string | undefined; typeMeta: string | undefined; allowNoIndex: true | undefined; + runtimeFieldMap: 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 index eab6ae9bf9033..0030adf1261e4 100644 --- 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 @@ -14,6 +14,7 @@ getComputedFields(): { field: any; format: string; }[]; + runtimeFields: Record; }; ``` Returns: @@ -25,5 +26,6 @@ getComputedFields(): { field: any; format: string; }[]; + runtimeFields: Record; }` 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 index 70c37ba1b3926..97d1cd9115262 100644 --- 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 @@ -45,6 +45,7 @@ export declare class IndexPattern implements IIndexPattern | Method | Modifiers | Description | | --- | --- | --- | +| [addRuntimeField(name, runtimeField)](./kibana-plugin-plugins-data-server.indexpattern.addruntimefield.md) | | Add a runtime field - Appended to existing mapped field or a new field is created as appropriate | | [addScriptedField(name, script, fieldType)](./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 | @@ -58,6 +59,7 @@ export declare class IndexPattern implements IIndexPattern | [getTimeField()](./kibana-plugin-plugins-data-server.indexpattern.gettimefield.md) | | | | [isTimeBased()](./kibana-plugin-plugins-data-server.indexpattern.istimebased.md) | | | | [isTimeNanosBased()](./kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md) | | | +| [removeRuntimeField(name)](./kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md) | | Remove a runtime field - removed from mapped field or removed unmapped field as appropriate | | [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | | [setFieldAttrs(fieldName, attrName, value)](./kibana-plugin-plugins-data-server.indexpattern.setfieldattrs.md) | | | | [setFieldCount(fieldName, count)](./kibana-plugin-plugins-data-server.indexpattern.setfieldcount.md) | | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md new file mode 100644 index 0000000000000..da8e7e40a7fac --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.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) > [removeRuntimeField](./kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md) + +## IndexPattern.removeRuntimeField() method + +Remove a runtime field - removed from mapped field or removed unmapped field as appropriate + +Signature: + +```typescript +removeRuntimeField(name: string): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`void` + 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 bfc7f65425f9c..20af97ecc8761 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 @@ -21,6 +21,7 @@ export interface IndexPatternAttributes | [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpatternattributes.fieldformatmap.md) | string | | | [fields](./kibana-plugin-plugins-data-server.indexpatternattributes.fields.md) | string | | | [intervalName](./kibana-plugin-plugins-data-server.indexpatternattributes.intervalname.md) | string | | +| [runtimeFieldMap](./kibana-plugin-plugins-data-server.indexpatternattributes.runtimefieldmap.md) | string | | | [sourceFilters](./kibana-plugin-plugins-data-server.indexpatternattributes.sourcefilters.md) | string | | | [timeFieldName](./kibana-plugin-plugins-data-server.indexpatternattributes.timefieldname.md) | string | | | [title](./kibana-plugin-plugins-data-server.indexpatternattributes.title.md) | string | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.runtimefieldmap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.runtimefieldmap.md new file mode 100644 index 0000000000000..1e0dff2ad0e46 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.runtimefieldmap.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) > [runtimeFieldMap](./kibana-plugin-plugins-data-server.indexpatternattributes.runtimefieldmap.md) + +## IndexPatternAttributes.runtimeFieldMap property + +Signature: + +```typescript +runtimeFieldMap?: string; +``` diff --git a/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap b/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap index 3e09fa449a1aa..4ef61ec0f2557 100644 --- a/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap +++ b/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap @@ -57,9 +57,16 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": undefined, "lang": "lang", "name": "name", "readFromDocValues": false, + "runtimeField": Object { + "script": Object { + "source": "emit('hello world')", + }, + "type": "keyword", + }, "script": "script", "scripted": true, "searchable": true, diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts index bce75f9932479..8a73abb3c7d83 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts @@ -9,7 +9,7 @@ import { IndexPatternField } from './index_pattern_field'; import { IndexPattern } from '../index_patterns'; import { KBN_FIELD_TYPES, FieldFormat } from '../../../common'; -import { FieldSpec } from '../types'; +import { FieldSpec, RuntimeField } from '../types'; describe('Field', function () { function flatten(obj: Record) { @@ -42,6 +42,12 @@ describe('Field', function () { } as unknown) as IndexPattern, $$spec: ({} as unknown) as FieldSpec, conflictDescriptions: { a: ['b', 'c'], d: ['e'] }, + runtimeField: { + type: 'keyword' as RuntimeField['type'], + script: { + source: "emit('hello world')", + }, + }, }; it('the correct properties are writable', () => { 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 540563c3a8cfc..ed6c4bd40d561 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 @@ -6,9 +6,10 @@ * Public License, v 1. */ +import type { RuntimeField } from '../types'; import { KbnFieldType, getKbnFieldType } from '../../kbn_field_types'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; -import { IFieldType } from './types'; +import type { IFieldType } from './types'; import { FieldSpec, IndexPattern } from '../..'; import { shortenDottedString } from '../../utils'; @@ -35,6 +36,14 @@ export class IndexPatternField implements IFieldType { this.spec.count = count; } + public get runtimeField() { + return this.spec.runtimeField; + } + + public set runtimeField(runtimeField: RuntimeField | undefined) { + this.spec.runtimeField = runtimeField; + } + /** * Script field code */ @@ -117,6 +126,13 @@ export class IndexPatternField implements IFieldType { return this.spec.subType; } + /** + * Is the field part of the index mapping? + */ + public get isMapped() { + return this.spec.isMapped; + } + // not writable, not serialized public get sortable() { return ( @@ -181,6 +197,8 @@ export class IndexPatternField implements IFieldType { format: getFormatterForField ? getFormatterForField(this).toJSON() : undefined, customLabel: this.customLabel, shortDotsEnable: this.spec.shortDotsEnable, + runtimeField: this.runtimeField, + isMapped: this.isMapped, }; } } 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 76de2b2662bb0..4aadddfad3b97 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 @@ -20,9 +20,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "@tags", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -44,9 +46,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "@timestamp", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -68,9 +72,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "_id", "readFromDocValues": false, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -92,9 +98,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "_source", "readFromDocValues": false, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -116,9 +124,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "_type", "readFromDocValues": false, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -140,9 +150,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "area", "readFromDocValues": false, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -164,9 +176,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "bytes", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -188,9 +202,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "custom_user_field", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -212,9 +228,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "extension", "readFromDocValues": false, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -236,9 +254,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "extension.keyword", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -264,9 +284,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "geo.coordinates", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -288,9 +310,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "geo.src", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -312,9 +336,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "hashed", "readFromDocValues": false, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -336,9 +362,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "ip", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -360,9 +388,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "machine.os", "readFromDocValues": false, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -384,9 +414,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "machine.os.raw", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -412,9 +444,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "non-filterable", "readFromDocValues": false, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": false, @@ -436,9 +470,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "non-sortable", "readFromDocValues": false, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": false, @@ -460,9 +496,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "phpmemory", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -484,9 +522,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "point", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -508,9 +548,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "request_body", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -518,6 +560,35 @@ Object { "subType": undefined, "type": "attachment", }, + "runtime_field": Object { + "aggregatable": false, + "conflictDescriptions": undefined, + "count": 0, + "customLabel": undefined, + "esTypes": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, + "isMapped": undefined, + "lang": undefined, + "name": "runtime_field", + "readFromDocValues": false, + "runtimeField": Object { + "script": Object { + "source": "emit('hello world')", + }, + "type": "keyword", + }, + "script": undefined, + "scripted": false, + "searchable": false, + "shortDotsEnable": false, + "subType": undefined, + "type": undefined, + }, "script date": Object { "aggregatable": true, "conflictDescriptions": undefined, @@ -532,9 +603,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": false, "lang": "painless", "name": "script date", "readFromDocValues": false, + "runtimeField": undefined, "script": "1234", "scripted": true, "searchable": true, @@ -556,9 +629,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": false, "lang": "expression", "name": "script murmur3", "readFromDocValues": false, + "runtimeField": undefined, "script": "1234", "scripted": true, "searchable": true, @@ -580,9 +655,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": false, "lang": "expression", "name": "script number", "readFromDocValues": false, + "runtimeField": undefined, "script": "1234", "scripted": true, "searchable": true, @@ -604,9 +681,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": false, "lang": "expression", "name": "script string", "readFromDocValues": false, + "runtimeField": undefined, "script": "'i am a string'", "scripted": true, "searchable": true, @@ -628,9 +707,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "ssl", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -652,9 +733,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "time", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -676,9 +759,11 @@ Object { "pattern": "$0,0.[00]", }, }, + "isMapped": true, "lang": undefined, "name": "utc_time", "readFromDocValues": true, + "runtimeField": undefined, "script": undefined, "scripted": false, "searchable": true, @@ -689,6 +774,14 @@ Object { }, "id": "test-pattern", "intervalName": undefined, + "runtimeFieldMap": Object { + "runtime_field": Object { + "script": Object { + "source": "emit('hello world')", + }, + "type": "keyword", + }, + }, "sourceFilters": undefined, "timeFieldName": "timestamp", "title": "title", 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 index bad74430b8966..d6da4adac81a4 100644 --- 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 @@ -10,6 +10,7 @@ Object { "fields": Object {}, "id": "id", "intervalName": undefined, + "runtimeFieldMap": Object {}, "sourceFilters": Array [ Object { "value": "item1", diff --git a/src/plugins/data/common/index_patterns/index_patterns/fixtures/logstash_fields.js b/src/plugins/data/common/index_patterns/index_patterns/fixtures/logstash_fields.js index 3e81b9234ee64..2bcb8df34cf02 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/fixtures/logstash_fields.js +++ b/src/plugins/data/common/index_patterns/index_patterns/fixtures/logstash_fields.js @@ -68,6 +68,7 @@ function stubbedLogstashFields() { lang, scripted, subType, + isMapped: !scripted, }; }); } 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 bb7ed17f9e608..4f6e83460aecf 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 @@ -18,9 +18,27 @@ import { IndexPatternField } from '../fields'; import { fieldFormatsMock } from '../../field_formats/mocks'; import { FieldFormat } from '../..'; +import { RuntimeField } from '../types'; class MockFieldFormatter {} +const runtimeFieldScript = { + type: 'keyword' as RuntimeField['type'], + script: { + source: "emit('hello world')", + }, +}; + +const runtimeFieldMap = { + runtime_field: runtimeFieldScript, +}; + +const runtimeField = { + name: 'runtime_field', + runtimeField: runtimeFieldScript, + scripted: false, +}; + fieldFormatsMock.getInstance = jest.fn().mockImplementation(() => new MockFieldFormatter()) as any; // helper function to create index patterns @@ -32,7 +50,15 @@ function create(id: string) { } = stubbedSavedObjectIndexPattern(id); return new IndexPattern({ - spec: { id, type, version, timeFieldName, fields, title }, + spec: { + id, + type, + version, + timeFieldName, + fields: { ...fields, runtime_field: runtimeField }, + title, + runtimeFieldMap, + }, fieldFormats: fieldFormatsMock, shortDotsEnable: false, metaFields: [], @@ -53,6 +79,10 @@ describe('IndexPattern', () => { expect(indexPattern).toHaveProperty('getNonScriptedFields'); expect(indexPattern).toHaveProperty('addScriptedField'); expect(indexPattern).toHaveProperty('removeScriptedField'); + expect(indexPattern).toHaveProperty('addScriptedField'); + expect(indexPattern).toHaveProperty('removeScriptedField'); + expect(indexPattern).toHaveProperty('addRuntimeField'); + expect(indexPattern).toHaveProperty('removeRuntimeField'); // properties expect(indexPattern).toHaveProperty('fields'); @@ -65,6 +95,7 @@ describe('IndexPattern', () => { expect(indexPattern.fields[0]).toHaveProperty('filterable'); expect(indexPattern.fields[0]).toHaveProperty('sortable'); expect(indexPattern.fields[0]).toHaveProperty('scripted'); + expect(indexPattern.fields[0]).toHaveProperty('isMapped'); }); }); @@ -98,6 +129,12 @@ describe('IndexPattern', () => { expect(docValueFieldNames).toContain('utc_time'); }); + test('should return runtimeField', () => { + expect(indexPattern.getComputedFields().runtimeFields).toEqual({ + runtime_field: runtimeFieldScript, + }); + }); + test('should request date field doc values in date_time format', () => { const { docvalueFields } = indexPattern.getComputedFields(); const timestampField = docvalueFields.find((field) => field.field === '@timestamp'); @@ -117,6 +154,7 @@ describe('IndexPattern', () => { const notScriptedNames = mockLogStashFields() .filter((item: IndexPatternField) => item.scripted === false) .map((item: IndexPatternField) => item.name); + notScriptedNames.push('runtime_field'); const respNames = map(indexPattern.getNonScriptedFields(), 'name'); expect(respNames).toEqual(notScriptedNames); @@ -185,6 +223,52 @@ describe('IndexPattern', () => { }); }); + describe('addRuntimeField and removeRuntimeField', () => { + const runtime = { + type: 'keyword' as RuntimeField['type'], + script: { + source: "emit('hello world');", + }, + }; + + beforeEach(() => { + const formatter = { + toJSON: () => ({ id: 'bytes' }), + } as FieldFormat; + indexPattern.getFormatterForField = () => formatter; + }); + + test('add and remove runtime field to existing field', () => { + indexPattern.addRuntimeField('@tags', runtime); + expect(indexPattern.toSpec().runtimeFieldMap).toEqual({ + '@tags': runtime, + runtime_field: runtimeField.runtimeField, + }); + expect(indexPattern.toSpec()!.fields!['@tags'].runtimeField).toEqual(runtime); + + indexPattern.removeRuntimeField('@tags'); + expect(indexPattern.toSpec().runtimeFieldMap).toEqual({ + runtime_field: runtimeField.runtimeField, + }); + expect(indexPattern.toSpec()!.fields!['@tags'].runtimeField).toBeUndefined(); + }); + + test('add and remove runtime field as new field', () => { + indexPattern.addRuntimeField('new_field', runtime); + expect(indexPattern.toSpec().runtimeFieldMap).toEqual({ + runtime_field: runtimeField.runtimeField, + new_field: runtime, + }); + expect(indexPattern.toSpec()!.fields!.new_field.runtimeField).toEqual(runtime); + + indexPattern.removeRuntimeField('new_field'); + expect(indexPattern.toSpec().runtimeFieldMap).toEqual({ + runtime_field: runtimeField.runtimeField, + }); + expect(indexPattern.toSpec()!.fields!.new_field).toBeUndefined(); + }); + }); + describe('getFormatterForField', () => { test('should return the default one for empty objects', () => { indexPattern.setFieldFormat('scriptedFieldWithEmptyFormatter', {}); 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 452c663d96716..144d38fe15909 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 @@ -8,6 +8,7 @@ import _, { each, reject } from 'lodash'; import { FieldAttrs, FieldAttrSet } from '../..'; +import type { RuntimeField } from '../types'; import { DuplicateField } from '../../../../kibana_utils/common'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../../../common'; @@ -17,6 +18,7 @@ import { flattenHitWrapper } from './flatten_hit'; import { FieldFormatsStartCommon, FieldFormat } from '../../field_formats'; import { IndexPatternSpec, TypeMeta, SourceFilter, IndexPatternFieldMap } from '../types'; import { SerializedFieldFormat } from '../../../../expressions/common'; +import { castEsToKbnFieldTypeName } from '../../kbn_field_types'; interface IndexPatternDeps { spec?: IndexPatternSpec; @@ -74,6 +76,8 @@ export class IndexPattern implements IIndexPattern { private shortDotsEnable: boolean = false; private fieldFormats: FieldFormatsStartCommon; private fieldAttrs: FieldAttrs; + private runtimeFieldMap: Record; + /** * prevents errors when index pattern exists before indices */ @@ -115,6 +119,7 @@ export class IndexPattern implements IIndexPattern { this.fieldAttrs = spec.fieldAttrs || {}; this.intervalName = spec.intervalName; this.allowNoIndex = spec.allowNoIndex || false; + this.runtimeFieldMap = spec.runtimeFieldMap || {}; } /** @@ -160,7 +165,8 @@ export class IndexPattern implements IIndexPattern { return { storedFields: ['*'], scriptFields, - docvalueFields: [], + docvalueFields: [] as Array<{ field: string; format: string }>, + runtimeFields: {}, }; } @@ -192,6 +198,7 @@ export class IndexPattern implements IIndexPattern { storedFields: ['*'], scriptFields, docvalueFields, + runtimeFields: this.runtimeFieldMap, }; } @@ -210,6 +217,7 @@ export class IndexPattern implements IIndexPattern { typeMeta: this.typeMeta, type: this.type, fieldFormats: this.fieldFormatMap, + runtimeFieldMap: this.runtimeFieldMap, fieldAttrs: this.fieldAttrs, intervalName: this.intervalName, allowNoIndex: this.allowNoIndex, @@ -305,6 +313,7 @@ export class IndexPattern implements IIndexPattern { ? undefined : JSON.stringify(this.fieldFormatMap); const fieldAttrs = this.getFieldAttrs(); + const runtimeFieldMap = this.runtimeFieldMap; return { fieldAttrs: fieldAttrs ? JSON.stringify(fieldAttrs) : undefined, @@ -319,6 +328,7 @@ export class IndexPattern implements IIndexPattern { type: this.type, typeMeta: this.typeMeta ? JSON.stringify(this.typeMeta) : undefined, allowNoIndex: this.allowNoIndex ? this.allowNoIndex : undefined, + runtimeFieldMap: runtimeFieldMap ? JSON.stringify(runtimeFieldMap) : undefined, }; } @@ -340,6 +350,51 @@ export class IndexPattern implements IIndexPattern { ); } + /** + * Add a runtime field - Appended to existing mapped field or a new field is + * created as appropriate + * @param name Field name + * @param runtimeField Runtime field definition + */ + + addRuntimeField(name: string, runtimeField: RuntimeField) { + const existingField = this.getFieldByName(name); + if (existingField) { + existingField.runtimeField = runtimeField; + } else { + this.fields.add({ + name, + runtimeField, + type: castEsToKbnFieldTypeName(runtimeField.type), + aggregatable: true, + searchable: true, + count: 0, + readFromDocValues: false, + }); + } + this.runtimeFieldMap[name] = runtimeField; + } + + /** + * Remove a runtime field - removed from mapped field or removed unmapped + * field as appropriate + * @param name Field name + */ + + removeRuntimeField(name: string) { + const existingField = this.getFieldByName(name); + if (existingField) { + if (existingField.isMapped) { + // mapped field, remove runtimeField def + existingField.runtimeField = undefined; + } else { + // runtimeField only + this.fields.remove(existingField); + } + } + delete this.runtimeFieldMap[name]; + } + /** * Get formatter for a given field name. Return undefined if none exists * @param field 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 80cb8a55fa0a0..60436da530b63 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 @@ -11,6 +11,7 @@ import { PublicMethodsOf } from '@kbn/utility-types'; import { SavedObjectsClientCommon } from '../..'; import { createIndexPatternCache } from '.'; +import type { RuntimeField } from '../types'; import { IndexPattern } from './index_pattern'; import { createEnsureDefaultIndexPattern, @@ -34,6 +35,7 @@ import { SavedObjectNotFound } from '../../../../kibana_utils/common'; import { IndexPatternMissingIndices } from '../lib'; import { findByTitle } from '../utils'; import { DuplicateIndexPatternError } from '../errors'; +import { castEsToKbnFieldTypeName } from '../../kbn_field_types'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const savedObjectType = 'index-pattern'; @@ -247,7 +249,8 @@ export class IndexPatternsService { */ refreshFields = async (indexPattern: IndexPattern) => { try { - const fields = await this.getFieldsForIndexPattern(indexPattern); + const fields = (await this.getFieldsForIndexPattern(indexPattern)) as FieldSpec[]; + fields.forEach((field) => (field.isMapped = true)); const scripted = indexPattern.getScriptedFields().map((field) => field.spec); const fieldAttrs = indexPattern.getFieldAttrs(); const fieldsWithSavedAttrs = Object.values( @@ -288,6 +291,7 @@ export class IndexPatternsService { try { let updatedFieldList: FieldSpec[]; const newFields = (await this.getFieldsForWildcard(options)) as FieldSpec[]; + newFields.forEach((field) => (field.isMapped = true)); // If allowNoIndex, only update field list if field caps finds fields. To support // beats creating index pattern and dashboard before docs @@ -347,6 +351,7 @@ export class IndexPatternsService { fields, sourceFilters, fieldFormatMap, + runtimeFieldMap, typeMeta, type, fieldAttrs, @@ -359,6 +364,9 @@ export class IndexPatternsService { const parsedFieldFormatMap = fieldFormatMap ? JSON.parse(fieldFormatMap) : {}; const parsedFields: FieldSpec[] = fields ? JSON.parse(fields) : []; const parsedFieldAttrs: FieldAttrs = fieldAttrs ? JSON.parse(fieldAttrs) : {}; + const parsedRuntimeFieldMap: Record = runtimeFieldMap + ? JSON.parse(runtimeFieldMap) + : {}; return { id, @@ -373,6 +381,7 @@ export class IndexPatternsService { fieldFormats: parsedFieldFormatMap, fieldAttrs: parsedFieldAttrs, allowNoIndex, + runtimeFieldMap: parsedRuntimeFieldMap, }; }; @@ -387,7 +396,7 @@ export class IndexPatternsService { } const spec = this.savedObjectToSpec(savedObject); - const { title, type, typeMeta } = spec; + const { title, type, typeMeta, runtimeFieldMap } = spec; spec.fieldAttrs = savedObject.attributes.fieldAttrs ? JSON.parse(savedObject.attributes.fieldAttrs) : {}; @@ -406,6 +415,22 @@ export class IndexPatternsService { }, spec.fieldAttrs ); + // APPLY RUNTIME FIELDS + for (const [key, value] of Object.entries(runtimeFieldMap || {})) { + if (spec.fields[key]) { + spec.fields[key].runtimeField = value; + } else { + spec.fields[key] = { + name: key, + type: castEsToKbnFieldTypeName(value.type), + runtimeField: value, + aggregatable: true, + searchable: true, + count: 0, + readFromDocValues: false, + }; + } + } } catch (err) { if (err instanceof IndexPatternMissingIndices) { this.onNotification({ diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 9f9a26604a0e5..467b5125f0327 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -14,6 +14,14 @@ import { SerializedFieldFormat } from '../../../expressions/common'; import { KBN_FIELD_TYPES, IndexPatternField, FieldFormat } from '..'; export type FieldFormatMap = Record; +const RUNTIME_FIELD_TYPES = ['keyword', 'long', 'double', 'date', 'ip', 'boolean'] as const; +type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; +export interface RuntimeField { + type: RuntimeType; + script: { + source: string; + }; +} /** * IIndexPattern allows for an IndexPattern OR an index pattern saved object @@ -51,6 +59,7 @@ export interface IndexPatternAttributes { sourceFilters?: string; fieldFormatMap?: string; fieldAttrs?: string; + runtimeFieldMap?: string; /** * prevents errors when index pattern exists before indices */ @@ -199,8 +208,10 @@ export interface FieldSpec { subType?: IFieldSubType; indexed?: boolean; customLabel?: string; + runtimeField?: RuntimeField; // not persisted shortDotsEnable?: boolean; + isMapped?: boolean; } export type IndexPatternFieldMap = Record; @@ -230,6 +241,7 @@ export interface IndexPatternSpec { typeMeta?: TypeMeta; type?: string; fieldFormats?: Record; + runtimeFieldMap?: Record; fieldAttrs?: FieldAttrs; allowNoIndex?: boolean; } diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 0b9c60e94a198..6d7654c6659f2 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -20,6 +20,7 @@ const getComputedFields = () => ({ storedFields: [], scriptFields: {}, docvalueFields: [], + runtimeFields: {}, }); const mockSource = { excludes: ['foo-*'] }; @@ -37,6 +38,13 @@ const indexPattern2 = ({ getSourceFiltering: () => mockSource2, } as unknown) as IndexPattern; +const runtimeFieldDef = { + type: 'keyword', + script: { + source: "emit('hello world')", + }, +}; + describe('SearchSource', () => { let mockSearchMethod: any; let searchSourceDependencies: SearchSourceDependencies; @@ -82,12 +90,14 @@ describe('SearchSource', () => { describe('computed fields handling', () => { test('still provides computed fields when no fields are specified', async () => { + const runtimeFields = { runtime_field: runtimeFieldDef }; searchSource.setField('index', ({ ...indexPattern, getComputedFields: () => ({ storedFields: ['hello'], scriptFields: { world: {} }, docvalueFields: ['@timestamp'], + runtimeFields, }), } as unknown) as IndexPattern); @@ -95,6 +105,7 @@ describe('SearchSource', () => { expect(request.stored_fields).toEqual(['hello']); expect(request.script_fields).toEqual({ world: {} }); expect(request.fields).toEqual(['@timestamp']); + expect(request.runtime_mappings).toEqual(runtimeFields); }); test('never includes docvalue_fields', async () => { @@ -390,15 +401,23 @@ describe('SearchSource', () => { }); test('filters request when a specific list of fields is provided with fieldsFromSource', async () => { + const runtimeFields = { runtime_field: runtimeFieldDef, runtime_field_b: runtimeFieldDef }; searchSource.setField('index', ({ ...indexPattern, getComputedFields: () => ({ storedFields: ['*'], scriptFields: { hello: {}, world: {} }, docvalueFields: ['@timestamp', 'date'], + runtimeFields, }), } as unknown) as IndexPattern); - searchSource.setField('fieldsFromSource', ['hello', '@timestamp', 'foo-a', 'bar']); + searchSource.setField('fieldsFromSource', [ + 'hello', + '@timestamp', + 'foo-a', + 'bar', + 'runtime_field', + ]); const request = await searchSource.getSearchRequestBody(); expect(request._source).toEqual({ @@ -407,6 +426,7 @@ describe('SearchSource', () => { expect(request.fields).toEqual(['@timestamp']); expect(request.script_fields).toEqual({ hello: {} }); expect(request.stored_fields).toEqual(['@timestamp', 'bar']); + expect(request.runtime_mappings).toEqual({ runtime_field: runtimeFieldDef }); }); test('filters request when a specific list of fields is provided with fieldsFromSource or fields', async () => { diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 0f0688c9fc11f..554e8385881f2 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -461,12 +461,13 @@ export class SearchSource { searchRequest.indexType = this.getIndexType(index); // get some special field types from the index pattern - const { docvalueFields, scriptFields, storedFields } = index + const { docvalueFields, scriptFields, storedFields, runtimeFields } = index ? index.getComputedFields() : { docvalueFields: [], scriptFields: {}, storedFields: ['*'], + runtimeFields: {}, }; const fieldListProvided = !!body.fields; @@ -481,6 +482,7 @@ export class SearchSource { ...scriptFields, }; body.stored_fields = storedFields; + body.runtime_mappings = runtimeFields || {}; // apply source filters from index pattern if specified by the user let filteredDocvalueFields = docvalueFields; @@ -518,13 +520,18 @@ export class SearchSource { body.script_fields, Object.keys(body.script_fields).filter((f) => uniqFieldNames.includes(f)) ); + body.runtime_mappings = pick( + body.runtime_mappings, + Object.keys(body.runtime_mappings).filter((f) => uniqFieldNames.includes(f)) + ); } // request the remaining fields from stored_fields just in case, since the // fields API does not handle stored fields - const remainingFields = difference(uniqFieldNames, Object.keys(body.script_fields)).filter( - Boolean - ); + const remainingFields = difference(uniqFieldNames, [ + ...Object.keys(body.script_fields), + ...Object.keys(body.runtime_mappings), + ]).filter(Boolean); // only include unique values body.stored_fields = [...new Set(remainingFields)]; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index d6bd896a584a4..28997de4517e7 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1265,6 +1265,7 @@ export type IMetricAggType = MetricAggType; 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, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); + addRuntimeField(name: string, runtimeField: RuntimeField): void; addScriptedField(name: string, script: string, fieldType?: string): Promise; readonly allowNoIndex: boolean; // (undocumented) @@ -1304,6 +1305,7 @@ export class IndexPattern implements IIndexPattern { type: string | undefined; typeMeta: string | undefined; allowNoIndex: true | undefined; + runtimeFieldMap: string | undefined; }; // (undocumented) getComputedFields(): { @@ -1313,6 +1315,7 @@ export class IndexPattern implements IIndexPattern { field: any; format: string; }[]; + runtimeFields: Record; }; // (undocumented) getFieldAttrs: () => { @@ -1352,6 +1355,7 @@ export class IndexPattern implements IIndexPattern { isTimeNanosBased(): boolean; // (undocumented) metaFields: string[]; + removeRuntimeField(name: string): void; removeScriptedField(fieldName: string): void; resetOriginalSavedObjectBody: () => void; // (undocumented) @@ -1402,6 +1406,8 @@ export interface IndexPatternAttributes { // (undocumented) intervalName?: string; // (undocumented) + runtimeFieldMap?: string; + // (undocumented) sourceFilters?: string; // (undocumented) timeFieldName?: string; @@ -1435,12 +1441,16 @@ export class IndexPatternField implements IFieldType { get esTypes(): string[] | undefined; // (undocumented) get filterable(): boolean; + get isMapped(): boolean | undefined; get lang(): string | undefined; set lang(lang: string | undefined); // (undocumented) get name(): string; // (undocumented) get readFromDocValues(): boolean; + // (undocumented) + get runtimeField(): RuntimeField | undefined; + set runtimeField(runtimeField: RuntimeField | undefined); get script(): string | undefined; set script(script: string | undefined); // (undocumented) @@ -1537,6 +1547,8 @@ export interface IndexPatternSpec { // @deprecated (undocumented) intervalName?: string; // (undocumented) + runtimeFieldMap?: Record; + // (undocumented) sourceFilters?: SourceFilter[]; // (undocumented) timeFieldName?: string; @@ -2580,8 +2592,9 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/meta_filter.ts:43: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:22: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:20: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:63:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:133:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:65:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:138:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:169:7 - (ae-forgotten-export) The symbol "RuntimeField" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:139:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/search_source/search_source.ts:186:7 - (ae-forgotten-export) The symbol "SearchFieldValue" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:56:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index ef8015ecaca26..6a96fd8209a8d 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -705,6 +705,7 @@ export type IMetricAggType = MetricAggType; 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, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); + addRuntimeField(name: string, runtimeField: RuntimeField): void; addScriptedField(name: string, script: string, fieldType?: string): Promise; readonly allowNoIndex: boolean; // (undocumented) @@ -746,6 +747,7 @@ export class IndexPattern implements IIndexPattern { type: string | undefined; typeMeta: string | undefined; allowNoIndex: true | undefined; + runtimeFieldMap: string | undefined; }; // (undocumented) getComputedFields(): { @@ -755,6 +757,7 @@ export class IndexPattern implements IIndexPattern { field: any; format: string; }[]; + runtimeFields: Record; }; // (undocumented) getFieldAttrs: () => { @@ -796,6 +799,7 @@ export class IndexPattern implements IIndexPattern { isTimeNanosBased(): boolean; // (undocumented) metaFields: string[]; + removeRuntimeField(name: string): void; removeScriptedField(fieldName: string): void; resetOriginalSavedObjectBody: () => void; // (undocumented) @@ -838,6 +842,8 @@ export interface IndexPatternAttributes { // (undocumented) intervalName?: string; // (undocumented) + runtimeFieldMap?: string; + // (undocumented) sourceFilters?: string; // (undocumented) timeFieldName?: string; @@ -1394,9 +1400,10 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // // src/plugins/data/common/es_query/filters/meta_filter.ts:42: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:43:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:50: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:63:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:133:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:52: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:65:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:138:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:169:7 - (ae-forgotten-export) The symbol "RuntimeField" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:29:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:29:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:46:23 - (ae-forgotten-export) The symbol "datatableToCSV" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts index f72d65dd2ee56..1394ceab1dd18 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts @@ -49,6 +49,7 @@ describe('getSharingData', () => { "should": Array [], }, }, + "runtime_mappings": Object {}, "script_fields": Object {}, "sort": Array [ Object { diff --git a/x-pack/test/functional/apps/maps/mvt_scaling.js b/x-pack/test/functional/apps/maps/mvt_scaling.js index b5c9ddcbd5e13..a7551aca78b52 100644 --- a/x-pack/test/functional/apps/maps/mvt_scaling.js +++ b/x-pack/test/functional/apps/maps/mvt_scaling.js @@ -29,7 +29,7 @@ export default function ({ getPageObjects, getService }) { //Source should be correct expect(mapboxStyle.sources[VECTOR_SOURCE_ID].tiles[0]).to.equal( - '/api/maps/mvt/getTile?x={x}&y={y}&z={z}&geometryFieldName=geometry&index=geo_shapes*&requestBody=(_source:(includes:!(geometry,prop1)),docvalue_fields:!(prop1),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),script_fields:(),size:10000,stored_fields:!(geometry,prop1))&geoFieldType=geo_shape' + '/api/maps/mvt/getTile?x={x}&y={y}&z={z}&geometryFieldName=geometry&index=geo_shapes*&requestBody=(_source:(includes:!(geometry,prop1)),docvalue_fields:!(prop1),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),runtime_mappings:(),script_fields:(),size:10000,stored_fields:!(geometry,prop1))&geoFieldType=geo_shape' ); //Should correctly load meta for style-rule (sigma is set to 1, opacity to 1) diff --git a/x-pack/test/functional/apps/maps/mvt_super_fine.js b/x-pack/test/functional/apps/maps/mvt_super_fine.js index 3de2f461bc855..aede736deb262 100644 --- a/x-pack/test/functional/apps/maps/mvt_super_fine.js +++ b/x-pack/test/functional/apps/maps/mvt_super_fine.js @@ -32,7 +32,7 @@ export default function ({ getPageObjects, getService }) { //Source should be correct expect(mapboxStyle.sources[MB_VECTOR_SOURCE_ID].tiles[0]).to.equal( - "/api/maps/mvt/getGridTile?x={x}&y={y}&z={z}&geometryFieldName=geo.coordinates&index=logstash-*&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:geo.coordinates)),max_of_bytes:(max:(field:bytes))),geotile_grid:(bounds:!n,field:geo.coordinates,precision:!n,shard_size:65535,size:65535))),fields:!((field:'@timestamp',format:date_time),(field:'relatedContent.article:modified_time',format:date_time),(field:'relatedContent.article:published_time',format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:('@timestamp':(format:strict_date_optional_time,gte:'2015-09-20T00:00:00.000Z',lte:'2015-09-20T01:00:00.000Z')))),must:!(),must_not:!(),should:!())),script_fields:(hour_of_day:(script:(lang:painless,source:'doc[!'@timestamp!'].value.getHour()'))),size:0,stored_fields:!('*'))&requestType=grid&geoFieldType=geo_point" + "/api/maps/mvt/getGridTile?x={x}&y={y}&z={z}&geometryFieldName=geo.coordinates&index=logstash-*&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:geo.coordinates)),max_of_bytes:(max:(field:bytes))),geotile_grid:(bounds:!n,field:geo.coordinates,precision:!n,shard_size:65535,size:65535))),fields:!((field:'@timestamp',format:date_time),(field:'relatedContent.article:modified_time',format:date_time),(field:'relatedContent.article:published_time',format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:('@timestamp':(format:strict_date_optional_time,gte:'2015-09-20T00:00:00.000Z',lte:'2015-09-20T01:00:00.000Z')))),must:!(),must_not:!(),should:!())),runtime_mappings:(),script_fields:(hour_of_day:(script:(lang:painless,source:'doc[!'@timestamp!'].value.getHour()'))),size:0,stored_fields:!('*'))&requestType=grid&geoFieldType=geo_point" ); //Should correctly load meta for style-rule (sigma is set to 1, opacity to 1) From f6689729eae7349ddddc535826385dca948cdff8 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Thu, 21 Jan 2021 21:10:52 +0100 Subject: [PATCH 72/83] Migrate authentication functionality to a new Elasticsearch client. (#87094) --- .../authentication_service.mock.ts | 8 +- .../authentication_service.test.ts | 133 +-- .../authentication/authentication_service.ts | 107 +-- .../authentication/authenticator.test.ts | 26 +- .../server/authentication/authenticator.ts | 10 +- .../security/server/authentication/index.ts | 6 +- .../providers/anonymous.test.ts | 61 +- .../authentication/providers/anonymous.ts | 5 +- .../authentication/providers/base.mock.ts | 2 +- .../server/authentication/providers/base.ts | 12 +- .../authentication/providers/basic.test.ts | 42 +- .../authentication/providers/http.test.ts | 33 +- .../authentication/providers/kerberos.test.ts | 169 ++-- .../authentication/providers/kerberos.ts | 40 +- .../authentication/providers/oidc.test.ts | 256 +++--- .../server/authentication/providers/oidc.ts | 55 +- .../authentication/providers/pki.test.ts | 161 ++-- .../server/authentication/providers/pki.ts | 15 +- .../authentication/providers/saml.test.ts | 485 ++++++---- .../server/authentication/providers/saml.ts | 65 +- .../authentication/providers/token.test.ts | 93 +- .../server/authentication/providers/token.ts | 14 +- .../server/authentication/tokens.test.ts | 252 +++--- .../security/server/authentication/tokens.ts | 24 +- .../elasticsearch_client_plugin.ts | 214 ----- .../elasticsearch_service.test.ts | 64 +- .../elasticsearch/elasticsearch_service.ts | 38 +- .../security/server/elasticsearch/index.ts | 3 +- x-pack/plugins/security/server/mocks.ts | 2 +- x-pack/plugins/security/server/plugin.test.ts | 7 +- x-pack/plugins/security/server/plugin.ts | 116 ++- .../security/server/routes/index.mock.ts | 2 +- .../plugins/security/server/routes/index.ts | 2 +- .../routes/session_management/info.test.ts | 4 +- .../server/routes/session_management/info.ts | 4 +- .../routes/users/change_password.test.ts | 5 +- .../server/routes/users/change_password.ts | 4 +- .../routes/views/access_agreement.test.ts | 4 +- .../server/routes/views/access_agreement.ts | 4 +- .../server/routes/views/logged_out.test.ts | 3 +- .../server/routes/views/logged_out.ts | 4 +- .../server/session_management/index.ts | 2 +- .../session_management/session_index.test.ts | 845 +++++++++--------- .../session_management/session_index.ts | 83 +- .../session_management_service.test.ts | 132 +-- .../session_management_service.ts | 58 +- 46 files changed, 1809 insertions(+), 1865 deletions(-) delete mode 100644 x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts diff --git a/x-pack/plugins/security/server/authentication/authentication_service.mock.ts b/x-pack/plugins/security/server/authentication/authentication_service.mock.ts index 06884611f3873..9c67cf611eb44 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.mock.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.mock.ts @@ -5,17 +5,11 @@ */ import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; -import type { - AuthenticationServiceSetup, - AuthenticationServiceStart, -} from './authentication_service'; +import type { AuthenticationServiceStart } from './authentication_service'; import { apiKeysMock } from './api_keys/api_keys.mock'; export const authenticationServiceMock = { - createSetup: (): jest.Mocked => ({ - getCurrentUser: jest.fn(), - }), createStart: (): DeeplyMockedKeys => ({ apiKeys: apiKeysMock.create(), login: jest.fn(), diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index 59771c5027012..942ddc202360b 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -25,11 +25,9 @@ import { sessionMock } from '../session_management/session.mock'; import type { AuthenticationHandler, AuthToolkit, - ILegacyClusterClient, KibanaRequest, Logger, LoggerFactory, - LegacyScopedClusterClient, HttpServiceSetup, HttpServiceStart, } from '../../../../../src/core/server'; @@ -46,47 +44,17 @@ describe('AuthenticationService', () => { let service: AuthenticationService; let logger: jest.Mocked; let mockSetupAuthenticationParams: { - legacyAuditLogger: jest.Mocked; - audit: jest.Mocked; - config: ConfigType; - loggers: LoggerFactory; http: jest.Mocked; - clusterClient: jest.Mocked; license: jest.Mocked; - getFeatureUsageService: () => jest.Mocked; - session: jest.Mocked>; }; - let mockScopedClusterClient: jest.Mocked>; beforeEach(() => { logger = loggingSystemMock.createLogger(); mockSetupAuthenticationParams = { - legacyAuditLogger: securityAuditLoggerMock.create(), - audit: auditServiceMock.create(), http: coreMock.createSetup().http, - config: createConfig( - ConfigSchema.validate({ - encryptionKey: 'ab'.repeat(16), - secureCookies: true, - cookieName: 'my-sid-cookie', - }), - loggingSystemMock.create().get(), - { isTLSEnabled: false } - ), - clusterClient: elasticsearchServiceMock.createLegacyClusterClient(), license: licenseMock.create(), - loggers: loggingSystemMock.create(), - getFeatureUsageService: jest - .fn() - .mockReturnValue(securityFeatureUsageServiceMock.createStartContract()), - session: sessionMock.create(), }; - mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockSetupAuthenticationParams.clusterClient.asScoped.mockReturnValue( - (mockScopedClusterClient as unknown) as jest.Mocked - ); - service = new AuthenticationService(logger); }); @@ -101,6 +69,42 @@ describe('AuthenticationService', () => { expect.any(Function) ); }); + }); + + describe('#start()', () => { + let mockStartAuthenticationParams: { + legacyAuditLogger: jest.Mocked; + audit: jest.Mocked; + config: ConfigType; + loggers: LoggerFactory; + http: jest.Mocked; + clusterClient: ReturnType; + featureUsageService: jest.Mocked; + session: jest.Mocked>; + }; + beforeEach(() => { + const coreStart = coreMock.createStart(); + mockStartAuthenticationParams = { + legacyAuditLogger: securityAuditLoggerMock.create(), + audit: auditServiceMock.create(), + config: createConfig( + ConfigSchema.validate({ + encryptionKey: 'ab'.repeat(16), + secureCookies: true, + cookieName: 'my-sid-cookie', + }), + loggingSystemMock.create().get(), + { isTLSEnabled: false } + ), + http: coreStart.http, + clusterClient: elasticsearchServiceMock.createClusterClient(), + loggers: loggingSystemMock.create(), + featureUsageService: securityFeatureUsageServiceMock.createStartContract(), + session: sessionMock.create(), + }; + + service.setup(mockSetupAuthenticationParams); + }); describe('authentication handler', () => { let authHandler: AuthenticationHandler; @@ -109,12 +113,7 @@ describe('AuthenticationService', () => { beforeEach(() => { mockAuthToolkit = httpServiceMock.createAuthToolkit(); - service.setup(mockSetupAuthenticationParams); - - expect(mockSetupAuthenticationParams.http.registerAuth).toHaveBeenCalledTimes(1); - expect(mockSetupAuthenticationParams.http.registerAuth).toHaveBeenCalledWith( - expect.any(Function) - ); + service.start(mockStartAuthenticationParams); authHandler = mockSetupAuthenticationParams.http.registerAuth.mock.calls[0][0]; authenticate = jest.requireMock('./authenticator').Authenticator.mock.instances[0] @@ -298,63 +297,7 @@ describe('AuthenticationService', () => { describe('getCurrentUser()', () => { let getCurrentUser: (r: KibanaRequest) => AuthenticatedUser | null; beforeEach(async () => { - getCurrentUser = service.setup(mockSetupAuthenticationParams).getCurrentUser; - }); - - it('returns `null` if Security is disabled', () => { - mockSetupAuthenticationParams.license.isEnabled.mockReturnValue(false); - - expect(getCurrentUser(httpServerMock.createKibanaRequest())).toBe(null); - }); - - it('returns user from the auth state.', () => { - const mockUser = mockAuthenticatedUser(); - - const mockAuthGet = mockSetupAuthenticationParams.http.auth.get as jest.Mock; - mockAuthGet.mockReturnValue({ state: mockUser }); - - const mockRequest = httpServerMock.createKibanaRequest(); - expect(getCurrentUser(mockRequest)).toBe(mockUser); - expect(mockAuthGet).toHaveBeenCalledTimes(1); - expect(mockAuthGet).toHaveBeenCalledWith(mockRequest); - }); - - it('returns null if auth state is not available.', () => { - const mockAuthGet = mockSetupAuthenticationParams.http.auth.get as jest.Mock; - mockAuthGet.mockReturnValue({}); - - const mockRequest = httpServerMock.createKibanaRequest(); - expect(getCurrentUser(mockRequest)).toBeNull(); - expect(mockAuthGet).toHaveBeenCalledTimes(1); - expect(mockAuthGet).toHaveBeenCalledWith(mockRequest); - }); - }); - }); - - describe('#start()', () => { - let mockStartAuthenticationParams: { - http: jest.Mocked; - clusterClient: ReturnType; - }; - beforeEach(() => { - const coreStart = coreMock.createStart(); - mockStartAuthenticationParams = { - http: coreStart.http, - clusterClient: elasticsearchServiceMock.createClusterClient(), - }; - service.setup(mockSetupAuthenticationParams); - }); - - describe('getCurrentUser()', () => { - let getCurrentUser: (r: KibanaRequest) => AuthenticatedUser | null; - beforeEach(async () => { - getCurrentUser = (await service.start(mockStartAuthenticationParams)).getCurrentUser; - }); - - it('returns `null` if Security is disabled', () => { - mockSetupAuthenticationParams.license.isEnabled.mockReturnValue(false); - - expect(getCurrentUser(httpServerMock.createKibanaRequest())).toBe(null); + getCurrentUser = service.start(mockStartAuthenticationParams).getCurrentUser; }); it('returns user from the auth state.', () => { diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index e435ae43f3bf3..3ab92d0bd211f 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -11,7 +11,6 @@ import type { Logger, HttpServiceSetup, IClusterClient, - ILegacyClusterClient, HttpServiceStart, } from '../../../../../src/core/server'; import type { SecurityLicense } from '../../common/licensing'; @@ -27,27 +26,19 @@ import { APIKeys } from './api_keys'; import { Authenticator, ProviderLoginAttempt } from './authenticator'; interface AuthenticationServiceSetupParams { - legacyAuditLogger: SecurityAuditLogger; - audit: AuditServiceSetup; - getFeatureUsageService: () => SecurityFeatureUsageServiceStart; - http: HttpServiceSetup; - clusterClient: ILegacyClusterClient; - config: ConfigType; + http: Pick; license: SecurityLicense; - loggers: LoggerFactory; - session: PublicMethodsOf; } interface AuthenticationServiceStartParams { - http: HttpServiceStart; + http: Pick; + config: ConfigType; clusterClient: IClusterClient; -} - -export interface AuthenticationServiceSetup { - /** - * @deprecated use `getCurrentUser` from the start contract instead - */ - getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null; + legacyAuditLogger: SecurityAuditLogger; + audit: AuditServiceSetup; + featureUsageService: SecurityFeatureUsageServiceStart; + session: PublicMethodsOf; + loggers: LoggerFactory; } export interface AuthenticationServiceStart { @@ -67,44 +58,13 @@ export interface AuthenticationServiceStart { export class AuthenticationService { private license!: SecurityLicense; - private authenticator!: Authenticator; + private authenticator?: Authenticator; constructor(private readonly logger: Logger) {} - setup({ - legacyAuditLogger: auditLogger, - audit, - getFeatureUsageService, - http, - clusterClient, - config, - license, - loggers, - session, - }: AuthenticationServiceSetupParams): AuthenticationServiceSetup { + setup({ http, license }: AuthenticationServiceSetupParams) { this.license = license; - const getCurrentUser = (request: KibanaRequest) => { - if (!license.isEnabled()) { - return null; - } - - return http.auth.get(request).state ?? null; - }; - - this.authenticator = new Authenticator({ - legacyAuditLogger: auditLogger, - audit, - loggers, - clusterClient, - basePath: http.basePath, - config: { authc: config.authc }, - getCurrentUser, - getFeatureUsageService, - license, - session, - }); - http.registerAuth(async (request, response, t) => { if (!license.isLicenseAvailable()) { this.logger.error('License is not available, authentication is not possible.'); @@ -123,6 +83,15 @@ export class AuthenticationService { return t.authenticated(); } + if (!this.authenticator) { + this.logger.error('Authentication sub-system is not fully initialized yet.'); + return response.customError({ + body: 'Authentication sub-system is not fully initialized yet.', + statusCode: 503, + headers: { 'Retry-After': '30' }, + }); + } + let authenticationResult; try { authenticationResult = await this.authenticator.authenticate(request); @@ -174,19 +143,40 @@ export class AuthenticationService { }); this.logger.debug('Successfully registered core authentication handler.'); - - return { - getCurrentUser, - }; } - start({ clusterClient, http }: AuthenticationServiceStartParams): AuthenticationServiceStart { + start({ + audit, + config, + clusterClient, + featureUsageService, + http, + legacyAuditLogger, + loggers, + session, + }: AuthenticationServiceStartParams): AuthenticationServiceStart { const apiKeys = new APIKeys({ clusterClient, logger: this.logger.get('api-key'), license: this.license, }); + const getCurrentUser = (request: KibanaRequest) => + http.auth.get(request).state ?? null; + + this.authenticator = new Authenticator({ + legacyAuditLogger, + audit, + loggers, + clusterClient, + basePath: http.basePath, + config: { authc: config.authc }, + getCurrentUser, + featureUsageService, + license: this.license, + session, + }); + return { apiKeys: { areAPIKeysEnabled: apiKeys.areAPIKeysEnabled.bind(apiKeys), @@ -206,12 +196,7 @@ export class AuthenticationService { * Retrieves currently authenticated user associated with the specified request. * @param request */ - getCurrentUser: (request: KibanaRequest) => { - if (!this.license.isEnabled()) { - return null; - } - return http.auth.get(request).state ?? null; - }, + getCurrentUser, }; } } diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 3d3946fde9f34..08d671d64179a 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -43,7 +43,7 @@ function getMockOptions({ legacyAuditLogger: securityAuditLoggerMock.create(), audit: auditServiceMock.create(), getCurrentUser: jest.fn(), - clusterClient: elasticsearchServiceMock.createLegacyClusterClient(), + clusterClient: elasticsearchServiceMock.createClusterClient(), basePath: httpServiceMock.createSetupContract().basePath, license: licenseMock.create(), loggers: loggingSystemMock.create(), @@ -53,9 +53,7 @@ function getMockOptions({ { isTLSEnabled: false } ), session: sessionMock.create(), - getFeatureUsageService: jest - .fn() - .mockReturnValue(securityFeatureUsageServiceMock.createStartContract()), + featureUsageService: securityFeatureUsageServiceMock.createStartContract(), }; } @@ -1880,9 +1878,7 @@ describe('Authenticator', () => { ); expect(mockOptions.session.update).not.toHaveBeenCalled(); - expect( - mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage - ).not.toHaveBeenCalled(); + expect(mockOptions.featureUsageService.recordPreAccessAgreementUsage).not.toHaveBeenCalled(); }); it('fails if cannot retrieve user session', async () => { @@ -1895,12 +1891,10 @@ describe('Authenticator', () => { ); expect(mockOptions.session.update).not.toHaveBeenCalled(); - expect( - mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage - ).not.toHaveBeenCalled(); + expect(mockOptions.featureUsageService.recordPreAccessAgreementUsage).not.toHaveBeenCalled(); }); - it('fails if license doesn allow access agreement acknowledgement', async () => { + it('fails if license does not allow access agreement acknowledgement', async () => { mockOptions.license.getFeatures.mockReturnValue({ allowAccessAgreement: false, } as SecurityLicenseFeatures); @@ -1912,9 +1906,7 @@ describe('Authenticator', () => { ); expect(mockOptions.session.update).not.toHaveBeenCalled(); - expect( - mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage - ).not.toHaveBeenCalled(); + expect(mockOptions.featureUsageService.recordPreAccessAgreementUsage).not.toHaveBeenCalled(); }); it('properly acknowledges access agreement for the authenticated user', async () => { @@ -1936,9 +1928,9 @@ describe('Authenticator', () => { } ); - expect( - mockOptions.getFeatureUsageService().recordPreAccessAgreementUsage - ).toHaveBeenCalledTimes(1); + expect(mockOptions.featureUsageService.recordPreAccessAgreementUsage).toHaveBeenCalledTimes( + 1 + ); }); }); }); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 85215ebf46fb4..af492bf247726 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -7,8 +7,8 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { KibanaRequest, LoggerFactory, - ILegacyClusterClient, IBasePath, + IClusterClient, } from '../../../../../src/core/server'; import { AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, @@ -68,13 +68,13 @@ export interface ProviderLoginAttempt { export interface AuthenticatorOptions { legacyAuditLogger: SecurityAuditLogger; audit: AuditServiceSetup; - getFeatureUsageService: () => SecurityFeatureUsageServiceStart; + featureUsageService: SecurityFeatureUsageServiceStart; getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null; config: Pick; basePath: IBasePath; license: SecurityLicense; loggers: LoggerFactory; - clusterClient: ILegacyClusterClient; + clusterClient: IClusterClient; session: PublicMethodsOf; } @@ -201,7 +201,7 @@ export class Authenticator { client: this.options.clusterClient, basePath: this.options.basePath, tokens: new Tokens({ - client: this.options.clusterClient, + client: this.options.clusterClient.asInternalUser, logger: this.options.loggers.get('tokens'), }), }; @@ -448,7 +448,7 @@ export class Authenticator { existingSessionValue.provider ); - this.options.getFeatureUsageService().recordPreAccessAgreementUsage(); + this.options.featureUsageService.recordPreAccessAgreementUsage(); } /** diff --git a/x-pack/plugins/security/server/authentication/index.ts b/x-pack/plugins/security/server/authentication/index.ts index c87a02c9545c1..e745e1c5717b3 100644 --- a/x-pack/plugins/security/server/authentication/index.ts +++ b/x-pack/plugins/security/server/authentication/index.ts @@ -5,11 +5,7 @@ */ export { canRedirectRequest } from './can_redirect_request'; -export { - AuthenticationService, - AuthenticationServiceSetup, - AuthenticationServiceStart, -} from './authentication_service'; +export { AuthenticationService, AuthenticationServiceStart } from './authentication_service'; export { AuthenticationResult } from './authentication_result'; export { DeauthenticationResult } from './deauthentication_result'; export { diff --git a/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts b/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts index 9674181e18750..a2db413319546 100644 --- a/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts @@ -4,11 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { errors } from '@elastic/elasticsearch'; + import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../mocks'; import { mockAuthenticationProviderOptions } from './base.mock'; -import { ILegacyClusterClient, ScopeableRequest } from '../../../../../../src/core/server'; +import { ScopeableRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { @@ -18,15 +21,14 @@ import { import { AnonymousAuthenticationProvider } from './anonymous'; function expectAuthenticateCall( - mockClusterClient: jest.Mocked, + mockClusterClient: ReturnType, scopeableRequest: ScopeableRequest ) { expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest); const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value; - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); + expect(mockScopedClusterClient.asCurrentUser.security.authenticate).toHaveBeenCalledTimes(1); } enum CredentialsType { @@ -75,8 +77,10 @@ describe('AnonymousAuthenticationProvider', () => { describe('`login` method', () => { it('succeeds if credentials are valid, and creates session and authHeaders', async () => { - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect( @@ -92,10 +96,13 @@ describe('AnonymousAuthenticationProvider', () => { it('fails if user cannot be retrieved during login attempt', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); - - const authenticationError = new Error('Some error'); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError); + const authenticationError = new errors.ResponseError( + securityMock.createApiResponse({ body: {} }) + ); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + authenticationError + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.login(request)).resolves.toEqual( @@ -155,8 +162,10 @@ describe('AnonymousAuthenticationProvider', () => { it('succeeds for non-AJAX requests if state is available.', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, {})).resolves.toEqual( @@ -169,8 +178,10 @@ describe('AnonymousAuthenticationProvider', () => { it('succeeds for AJAX requests if state is available.', async () => { const request = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, {})).resolves.toEqual( @@ -185,8 +196,10 @@ describe('AnonymousAuthenticationProvider', () => { it('non-AJAX requests can start a new session.', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request)).resolves.toEqual( @@ -199,9 +212,13 @@ describe('AnonymousAuthenticationProvider', () => { it('fails if credentials are not valid.', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); - const authenticationError = new Error('Forbidden'); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError); + const authenticationError = new errors.ResponseError( + securityMock.createApiResponse({ body: {} }) + ); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + authenticationError + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request)).resolves.toEqual( @@ -225,8 +242,10 @@ describe('AnonymousAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, {})).resolves.toEqual( diff --git a/x-pack/plugins/security/server/authentication/providers/anonymous.ts b/x-pack/plugins/security/server/authentication/providers/anonymous.ts index 1585b0592b356..249b4adea7bba 100644 --- a/x-pack/plugins/security/server/authentication/providers/anonymous.ts +++ b/x-pack/plugins/security/server/authentication/providers/anonymous.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, LegacyElasticsearchErrorHelpers } from '../../../../../../src/core/server'; +import { KibanaRequest } from '../../../../../../src/core/server'; +import { getErrorStatusCode } from '../../errors'; import { AuthenticationResult } from '../authentication_result'; import { canRedirectRequest } from '../can_redirect_request'; import { DeauthenticationResult } from '../deauthentication_result'; @@ -213,7 +214,7 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider // Create session only if it doesn't exist yet, otherwise keep it unchanged. return AuthenticationResult.succeeded(user, { authHeaders, state: state ? undefined : {} }); } catch (err) { - if (LegacyElasticsearchErrorHelpers.isNotAuthorizedError(err)) { + if (getErrorStatusCode(err) === 401) { if (!this.httpAuthorizationHeader) { this.logger.error( `Failed to authenticate anonymous request using Elasticsearch reserved anonymous user. Anonymous access may not be properly configured in Elasticsearch: ${err.message}` diff --git a/x-pack/plugins/security/server/authentication/providers/base.mock.ts b/x-pack/plugins/security/server/authentication/providers/base.mock.ts index 47d961bc8faf8..3eea6f9aadadf 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.mock.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.mock.ts @@ -16,7 +16,7 @@ export type MockAuthenticationProviderOptions = ReturnType< export function mockAuthenticationProviderOptions(options?: { name: string }) { return { - client: elasticsearchServiceMock.createLegacyClusterClient(), + client: elasticsearchServiceMock.createClusterClient(), logger: loggingSystemMock.create().get(), basePath: httpServiceMock.createBasePath(), tokens: { refresh: jest.fn(), invalidate: jest.fn() }, diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index f1845617c87a4..73449cf1077fe 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -10,8 +10,8 @@ import { KibanaRequest, Logger, HttpServiceSetup, - ILegacyClusterClient, Headers, + IClusterClient, } from '../../../../../../src/core/server'; import type { AuthenticatedUser } from '../../../common/model'; import type { AuthenticationInfo } from '../../elasticsearch'; @@ -25,7 +25,7 @@ import { Tokens } from '../tokens'; export interface AuthenticationProviderOptions { name: string; basePath: HttpServiceSetup['basePath']; - client: ILegacyClusterClient; + client: IClusterClient; logger: Logger; tokens: PublicMethodsOf; urls: { @@ -111,9 +111,11 @@ export abstract class BaseAuthenticationProvider { */ protected async getUser(request: KibanaRequest, authHeaders: Headers = {}) { return this.authenticationInfoToAuthenticatedUser( - await this.options.client - .asScoped({ headers: { ...request.headers, ...authHeaders } }) - .callAsCurrentUser('shield.authenticate') + ( + await this.options.client + .asScoped({ headers: { ...request.headers, ...authHeaders } }) + .asCurrentUser.security.authenticate() + ).body ); } diff --git a/x-pack/plugins/security/server/authentication/providers/basic.test.ts b/x-pack/plugins/security/server/authentication/providers/basic.test.ts index 4f93e2327da06..e7cf3d95b0827 100644 --- a/x-pack/plugins/security/server/authentication/providers/basic.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/basic.test.ts @@ -4,11 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { errors } from '@elastic/elasticsearch'; + import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../mocks'; import { mockAuthenticationProviderOptions } from './base.mock'; -import { ILegacyClusterClient, ScopeableRequest } from '../../../../../../src/core/server'; +import { ScopeableRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { BasicAuthenticationProvider } from './basic'; @@ -18,15 +21,14 @@ function generateAuthorizationHeader(username: string, password: string) { } function expectAuthenticateCall( - mockClusterClient: jest.Mocked, + mockClusterClient: ReturnType, scopeableRequest: ScopeableRequest ) { expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest); const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value; - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); + expect(mockScopedClusterClient.asCurrentUser.security.authenticate).toHaveBeenCalledTimes(1); } describe('BasicAuthenticationProvider', () => { @@ -45,8 +47,10 @@ describe('BasicAuthenticationProvider', () => { const credentials = { username: 'user', password: 'password' }; const authorization = generateAuthorizationHeader(credentials.username, credentials.password); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect( @@ -66,9 +70,13 @@ describe('BasicAuthenticationProvider', () => { const credentials = { username: 'user', password: 'password' }; const authorization = generateAuthorizationHeader(credentials.username, credentials.password); - const authenticationError = new Error('Some error'); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError); + const authenticationError = new errors.ResponseError( + securityMock.createApiResponse({ body: {} }) + ); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + authenticationError + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.login(request, credentials)).resolves.toEqual( @@ -149,8 +157,10 @@ describe('BasicAuthenticationProvider', () => { const user = mockAuthenticatedUser(); const authorization = generateAuthorizationHeader('user', 'password'); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, { authorization })).resolves.toEqual( @@ -164,9 +174,13 @@ describe('BasicAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); const authorization = generateAuthorizationHeader('user', 'password'); - const authenticationError = new Error('Forbidden'); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError); + const authenticationError = new errors.ResponseError( + securityMock.createApiResponse({ body: {} }) + ); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + authenticationError + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, { authorization })).resolves.toEqual( diff --git a/x-pack/plugins/security/server/authentication/providers/http.test.ts b/x-pack/plugins/security/server/authentication/providers/http.test.ts index 512a8ead2c32b..b8a2a110d45b0 100644 --- a/x-pack/plugins/security/server/authentication/providers/http.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/http.test.ts @@ -4,29 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import { errors } from '@elastic/elasticsearch'; + import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../mocks'; import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock'; -import { - LegacyElasticsearchErrorHelpers, - ILegacyClusterClient, - ScopeableRequest, -} from '../../../../../../src/core/server'; +import { ScopeableRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { HTTPAuthenticationProvider } from './http'; function expectAuthenticateCall( - mockClusterClient: jest.Mocked, + mockClusterClient: ReturnType, scopeableRequest: ScopeableRequest ) { expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest); const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value; - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); + expect(mockScopedClusterClient.asCurrentUser.security.authenticate).toHaveBeenCalledTimes(1); } describe('HTTPAuthenticationProvider', () => { @@ -58,7 +56,6 @@ describe('HTTPAuthenticationProvider', () => { await expect(provider.login()).resolves.toEqual(AuthenticationResult.notHandled()); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); }); }); @@ -73,7 +70,6 @@ describe('HTTPAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); }); it('does not handle authentication for requests with empty scheme in `authorization` header.', async () => { @@ -88,7 +84,6 @@ describe('HTTPAuthenticationProvider', () => { ).resolves.toEqual(AuthenticationResult.notHandled()); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); }); it('does not handle authentication via `authorization` header if scheme is not supported.', async () => { @@ -112,7 +107,6 @@ describe('HTTPAuthenticationProvider', () => { } expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); }); it('succeeds if authentication via `authorization` header with supported scheme succeeds.', async () => { @@ -126,8 +120,10 @@ describe('HTTPAuthenticationProvider', () => { ]) { const request = httpServerMock.createKibanaRequest({ headers: { authorization: header } }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); mockOptions.client.asScoped.mockClear(); @@ -149,7 +145,7 @@ describe('HTTPAuthenticationProvider', () => { }); it('fails if authentication via `authorization` header with supported scheme fails.', async () => { - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); + const failureReason = new errors.ResponseError(securityMock.createApiResponse({ body: {} })); for (const { schemes, header } of [ { schemes: ['basic'], header: 'Basic xxx' }, { schemes: ['bearer'], header: 'Bearer xxx' }, @@ -159,8 +155,10 @@ describe('HTTPAuthenticationProvider', () => { ]) { const request = httpServerMock.createKibanaRequest({ headers: { authorization: header } }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + failureReason + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); mockOptions.client.asScoped.mockClear(); @@ -188,7 +186,6 @@ describe('HTTPAuthenticationProvider', () => { await expect(provider.logout()).resolves.toEqual(DeauthenticationResult.notHandled()); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index d368bf90cf360..f8b7b42d1845b 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -5,32 +5,27 @@ */ import Boom from '@hapi/boom'; -import { errors } from 'elasticsearch'; +import { errors } from '@elastic/elasticsearch'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../mocks'; import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock'; -import { - LegacyElasticsearchErrorHelpers, - ILegacyClusterClient, - KibanaRequest, - ScopeableRequest, -} from '../../../../../../src/core/server'; +import { KibanaRequest, ScopeableRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { KerberosAuthenticationProvider } from './kerberos'; function expectAuthenticateCall( - mockClusterClient: jest.Mocked, + mockClusterClient: ReturnType, scopeableRequest: ScopeableRequest ) { expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest); const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value; - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); + expect(mockScopedClusterClient.asCurrentUser.security.authenticate).toHaveBeenCalledTimes(1); } describe('KerberosAuthenticationProvider', () => { @@ -47,8 +42,10 @@ describe('KerberosAuthenticationProvider', () => { it('does not handle requests that can be authenticated without `Negotiate` header.', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({}); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: {} }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(operation(request)).resolves.toEqual(AuthenticationResult.notHandled()); @@ -61,9 +58,9 @@ describe('KerberosAuthenticationProvider', () => { it('does not handle requests if backend does not support Kerberos.', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); @@ -77,17 +74,18 @@ describe('KerberosAuthenticationProvider', () => { it('fails with `Negotiate` challenge if backend supports Kerberos.', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( - new (errors.AuthenticationException as any)('Unauthorized', { + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ + statusCode: 401, body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, }) ); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue(failureReason); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(operation(request)).resolves.toEqual( - AuthenticationResult.failed(failureReason, { + AuthenticationResult.failed(Boom.unauthorized(), { authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); @@ -100,9 +98,12 @@ describe('KerberosAuthenticationProvider', () => { it('fails if request authentication is failed with non-401 error.', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); - const failureReason = new errors.ServiceUnavailable(); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + const failureReason = new errors.NoLivingConnectionsError( + 'Unavailable', + securityMock.createApiResponse({ statusCode: 500, body: {} }) + ); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue(failureReason); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(operation(request)).resolves.toEqual(AuthenticationResult.failed(failureReason)); @@ -118,11 +119,15 @@ describe('KerberosAuthenticationProvider', () => { headers: { authorization: 'negotiate spnego' }, }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - access_token: 'some-token', - refresh_token: 'some-refresh-token', - authentication: user, - }); + mockOptions.client.asInternalUser.security.getToken.mockResolvedValue( + securityMock.createApiResponse({ + body: { + access_token: 'some-token', + refresh_token: 'some-refresh-token', + authentication: user, + }, + }) + ); await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( @@ -135,7 +140,7 @@ describe('KerberosAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { + expect(mockOptions.client.asInternalUser.security.getToken).toHaveBeenCalledWith({ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' }, }); @@ -148,12 +153,16 @@ describe('KerberosAuthenticationProvider', () => { headers: { authorization: 'negotiate spnego' }, }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - access_token: 'some-token', - refresh_token: 'some-refresh-token', - kerberos_authentication_response_token: 'response-token', - authentication: user, - }); + mockOptions.client.asInternalUser.security.getToken.mockResolvedValue( + securityMock.createApiResponse({ + body: { + access_token: 'some-token', + refresh_token: 'some-refresh-token', + kerberos_authentication_response_token: 'response-token', + authentication: user, + }, + }) + ); await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( @@ -167,7 +176,7 @@ describe('KerberosAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { + expect(mockOptions.client.asInternalUser.security.getToken).toHaveBeenCalledWith({ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' }, }); @@ -179,12 +188,13 @@ describe('KerberosAuthenticationProvider', () => { headers: { authorization: 'negotiate spnego' }, }); - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( - new (errors.AuthenticationException as any)('Unauthorized', { + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ + statusCode: 401, body: { error: { header: { 'WWW-Authenticate': 'Negotiate response-token' } } }, }) ); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + mockOptions.client.asInternalUser.security.getToken.mockRejectedValue(failureReason); await expect(operation(request)).resolves.toEqual( AuthenticationResult.failed(Boom.unauthorized(), { @@ -192,7 +202,7 @@ describe('KerberosAuthenticationProvider', () => { }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { + expect(mockOptions.client.asInternalUser.security.getToken).toHaveBeenCalledWith({ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' }, }); @@ -204,12 +214,13 @@ describe('KerberosAuthenticationProvider', () => { headers: { authorization: 'negotiate spnego' }, }); - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( - new (errors.AuthenticationException as any)('Unauthorized', { + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ + statusCode: 401, body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, }) ); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + mockOptions.client.asInternalUser.security.getToken.mockRejectedValue(failureReason); await expect(operation(request)).resolves.toEqual( AuthenticationResult.failed(Boom.unauthorized(), { @@ -217,7 +228,7 @@ describe('KerberosAuthenticationProvider', () => { }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { + expect(mockOptions.client.asInternalUser.security.getToken).toHaveBeenCalledWith({ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' }, }); @@ -229,12 +240,14 @@ describe('KerberosAuthenticationProvider', () => { headers: { authorization: 'negotiate spnego' }, }); - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 401, body: {} }) + ); + mockOptions.client.asInternalUser.security.getToken.mockRejectedValue(failureReason); await expect(operation(request)).resolves.toEqual(AuthenticationResult.failed(failureReason)); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { + expect(mockOptions.client.asInternalUser.security.getToken).toHaveBeenCalledWith({ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' }, }); @@ -259,7 +272,7 @@ describe('KerberosAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.security.getToken).not.toHaveBeenCalled(); expect(request.headers.authorization).toBe('Bearer some-token'); }); @@ -277,7 +290,7 @@ describe('KerberosAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.security.getToken).not.toHaveBeenCalled(); expect(request.headers.authorization).toBe('Bearer some-token'); }); @@ -285,14 +298,15 @@ describe('KerberosAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'token', refreshToken: 'refresh-token' }; - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); mockOptions.tokens.refresh.mockResolvedValue(null); await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( - AuthenticationResult.failed(failureReason) + AuthenticationResult.failed(Boom.unauthorized()) ); expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1); @@ -306,7 +320,7 @@ describe('KerberosAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.security.getToken).not.toHaveBeenCalled(); }); it('does not start SPNEGO for Ajax requests.', async () => { @@ -316,7 +330,7 @@ describe('KerberosAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.security.getToken).not.toHaveBeenCalled(); }); it('succeeds if state contains a valid token.', async () => { @@ -328,8 +342,10 @@ describe('KerberosAuthenticationProvider', () => { }; const authorization = `Bearer ${tokenPair.accessToken}`; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( @@ -349,9 +365,9 @@ describe('KerberosAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); @@ -384,9 +400,11 @@ describe('KerberosAuthenticationProvider', () => { refreshToken: 'some-valid-refresh-token', }; - const failureReason = new errors.InternalServerError('Token is not valid!'); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 503, body: {} }) + ); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue(failureReason); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( @@ -396,23 +414,22 @@ describe('KerberosAuthenticationProvider', () => { expectAuthenticateCall(mockOptions.client, { headers: { authorization: `Bearer ${tokenPair.accessToken}` }, }); - - expect(mockScopedClusterClient.callAsInternalUser).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.security.getToken).not.toHaveBeenCalled(); expect(request.headers).not.toHaveProperty('authorization'); }); it('fails with `Negotiate` challenge if both access and refresh tokens from the state are expired and backend supports Kerberos.', async () => { - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( - new (errors.AuthenticationException as any)('Unauthorized', { - body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, - }) + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError( + securityMock.createApiResponse({ + statusCode: 401, + body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, + }) + ) ); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.tokens.refresh.mockResolvedValue(null); const nonAjaxRequest = httpServerMock.createKibanaRequest(); @@ -421,7 +438,7 @@ describe('KerberosAuthenticationProvider', () => { refreshToken: 'some-valid-refresh-token', }; await expect(provider.authenticate(nonAjaxRequest, nonAjaxTokenPair)).resolves.toEqual( - AuthenticationResult.failed(failureReason, { + AuthenticationResult.failed(Boom.unauthorized(), { authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); @@ -432,7 +449,7 @@ describe('KerberosAuthenticationProvider', () => { refreshToken: 'ajax-some-valid-refresh-token', }; await expect(provider.authenticate(ajaxRequest, ajaxTokenPair)).resolves.toEqual( - AuthenticationResult.failed(failureReason, { + AuthenticationResult.failed(Boom.unauthorized(), { authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); @@ -445,7 +462,7 @@ describe('KerberosAuthenticationProvider', () => { await expect( provider.authenticate(optionalAuthRequest, optionalAuthTokenPair) ).resolves.toEqual( - AuthenticationResult.failed(failureReason, { + AuthenticationResult.failed(Boom.unauthorized(), { authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index b7abed979164e..a02f6a8dfb945 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -5,12 +5,10 @@ */ import Boom from '@hapi/boom'; -import { - LegacyElasticsearchError, - LegacyElasticsearchErrorHelpers, - KibanaRequest, -} from '../../../../../../src/core/server'; +import { errors } from '@elastic/elasticsearch'; +import type { KibanaRequest } from '../../../../../../src/core/server'; import type { AuthenticationInfo } from '../../elasticsearch'; +import { getDetailedErrorMessage, getErrorStatusCode } from '../../errors'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { HTTPAuthorizationHeader } from '../http_authentication'; @@ -153,16 +151,21 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { authentication: AuthenticationInfo; }; try { - tokens = await this.options.client.callAsInternalUser('shield.getAccessToken', { - body: { grant_type: '_kerberos', kerberos_ticket: kerberosTicket }, - }); + tokens = ( + await this.options.client.asInternalUser.security.getToken({ + body: { grant_type: '_kerberos', kerberos_ticket: kerberosTicket }, + }) + ).body; } catch (err) { - this.logger.debug(`Failed to exchange SPNEGO token for an access token: ${err.message}`); + this.logger.debug( + `Failed to exchange SPNEGO token for an access token: ${getDetailedErrorMessage(err)}` + ); // Check if SPNEGO context wasn't established and we have a response token to return to the client. - const challenge = LegacyElasticsearchErrorHelpers.isNotAuthorizedError(err) - ? this.getNegotiateChallenge(err) - : undefined; + const challenge = + getErrorStatusCode(err) === 401 && err instanceof errors.ResponseError + ? this.getNegotiateChallenge(err) + : undefined; if (!challenge) { return AuthenticationResult.failed(err); } @@ -292,7 +295,7 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug('Trying to authenticate request via SPNEGO.'); // Try to authenticate current request with Elasticsearch to see whether it supports SPNEGO. - let elasticsearchError: LegacyElasticsearchError; + let elasticsearchError: errors.ResponseError; try { await this.getUser(request, { // We should send a fake SPNEGO token to Elasticsearch to make sure Kerberos realm is included @@ -306,7 +309,7 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { } catch (err) { // Fail immediately if we get unexpected error (e.g. ES isn't available). We should not touch // session cookie in this case. - if (!LegacyElasticsearchErrorHelpers.isNotAuthorizedError(err)) { + if (getErrorStatusCode(err) !== 401 || !(err instanceof errors.ResponseError)) { return AuthenticationResult.failed(err); } @@ -332,11 +335,14 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { * Extracts `Negotiate` challenge from the list of challenges returned with Elasticsearch error if any. * @param error Error to extract challenges from. */ - private getNegotiateChallenge(error: LegacyElasticsearchError) { + private getNegotiateChallenge(error: errors.ResponseError) { + // We extract headers from the original Elasticsearch error and not from the top-level `headers` + // property of the Elasticsearch client error since client merges multiple `WWW-Authenticate` + // headers into one using comma as a separator. That makes it hard to correctly parse the header + // since `WWW-Authenticate` values can also include commas. const challenges = ([] as string[]).concat( - (error.output.headers as { [key: string]: string })[WWWAuthenticateHeaderName] + error.body?.error?.header?.[WWWAuthenticateHeaderName] || [] ); - const negotiateChallenge = challenges.find((challenge) => challenge.toLowerCase().startsWith('negotiate') ); diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index 9988ddd99c395..8037b067852d8 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -5,16 +5,14 @@ */ import Boom from '@hapi/boom'; +import { errors } from '@elastic/elasticsearch'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../mocks'; import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock'; -import { - LegacyElasticsearchErrorHelpers, - KibanaRequest, - ILegacyScopedClusterClient, -} from '../../../../../../src/core/server'; +import { KibanaRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { OIDCAuthenticationProvider, OIDCLogin, ProviderLoginAttempt } from './oidc'; @@ -24,19 +22,18 @@ describe('OIDCAuthenticationProvider', () => { let provider: OIDCAuthenticationProvider; let mockOptions: MockAuthenticationProviderOptions; let mockUser: AuthenticatedUser; - let mockScopedClusterClient: jest.Mocked; + let mockScopedClusterClient: ReturnType< + typeof elasticsearchServiceMock.createScopedClusterClient + >; beforeEach(() => { mockOptions = mockAuthenticationProviderOptions({ name: 'oidc' }); - mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); + mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockUser = mockAuthenticatedUser({ authentication_provider: { type: 'oidc', name: 'oidc' } }); - mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method) => { - if (method === 'shield.authenticate') { - return mockUser; - } - - throw new Error(`Unexpected call to ${method}!`); - }); + mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: mockUser }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); provider = new OIDCAuthenticationProvider(mockOptions, { realm: 'oidc1' }); @@ -60,17 +57,21 @@ describe('OIDCAuthenticationProvider', () => { it('redirects third party initiated login attempts to the OpenId Connect Provider.', async () => { const request = httpServerMock.createKibanaRequest({ path: '/api/security/oidc/callback' }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - state: 'statevalue', - nonce: 'noncevalue', - redirect: - 'https://op-host/path/login?response_type=code' + - '&scope=openid%20profile%20email' + - '&client_id=s6BhdRkqt3' + - '&state=statevalue' + - '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc' + - '&login_hint=loginhint', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + state: 'statevalue', + nonce: 'noncevalue', + redirect: + 'https://op-host/path/login?response_type=code' + + '&scope=openid%20profile%20email' + + '&client_id=s6BhdRkqt3' + + '&state=statevalue' + + '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc' + + '&login_hint=loginhint', + }, + }) + ); await expect( provider.login(request, { @@ -97,7 +98,10 @@ describe('OIDCAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcPrepare', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/oidc/prepare', body: { iss: 'theissuer', login_hint: 'loginhint' }, }); }); @@ -105,17 +109,21 @@ describe('OIDCAuthenticationProvider', () => { it('redirects user initiated login attempts to the OpenId Connect Provider.', async () => { const request = httpServerMock.createKibanaRequest(); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - state: 'statevalue', - nonce: 'noncevalue', - redirect: - 'https://op-host/path/login?response_type=code' + - '&scope=openid%20profile%20email' + - '&client_id=s6BhdRkqt3' + - '&state=statevalue' + - '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc' + - '&login_hint=loginhint', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + state: 'statevalue', + nonce: 'noncevalue', + redirect: + 'https://op-host/path/login?response_type=code' + + '&scope=openid%20profile%20email' + + '&client_id=s6BhdRkqt3' + + '&state=statevalue' + + '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc' + + '&login_hint=loginhint', + }, + }) + ); await expect( provider.login(request, { @@ -141,7 +149,10 @@ describe('OIDCAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcPrepare', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/oidc/prepare', body: { realm: 'oidc1' }, }); }); @@ -149,8 +160,10 @@ describe('OIDCAuthenticationProvider', () => { it('fails if OpenID Connect authentication request preparation fails.', async () => { const request = httpServerMock.createKibanaRequest(); - const failureReason = new Error('Realm is misconfigured!'); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 503, body: {} }) + ); + mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason); await expect( provider.login(request, { @@ -159,8 +172,11 @@ describe('OIDCAuthenticationProvider', () => { }) ).resolves.toEqual(AuthenticationResult.failed(failureReason)); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcPrepare', { - body: { realm: `oidc1` }, + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/oidc/prepare', + body: { realm: 'oidc1' }, }); }); @@ -174,11 +190,15 @@ describe('OIDCAuthenticationProvider', () => { it('gets token and redirects user to requested URL if OIDC authentication response is valid.', async () => { const { request, attempt, expectedRedirectURI } = getMocks(); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - authentication: mockUser, - access_token: 'some-token', - refresh_token: 'some-refresh-token', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + authentication: mockUser, + access_token: 'some-token', + refresh_token: 'some-refresh-token', + }, + }) + ); await expect( provider.login(request, attempt, { @@ -198,17 +218,17 @@ describe('OIDCAuthenticationProvider', () => { }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.oidcAuthenticate', - { - body: { - state: 'statevalue', - nonce: 'noncevalue', - redirect_uri: expectedRedirectURI, - realm: 'oidc1', - }, - } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/oidc/authenticate', + body: { + state: 'statevalue', + nonce: 'noncevalue', + redirect_uri: expectedRedirectURI, + realm: 'oidc1', + }, + }); }); it('fails if authentication response is presented but session state does not contain the state parameter.', async () => { @@ -224,7 +244,7 @@ describe('OIDCAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('fails if authentication response is presented but session state does not contain redirect URL.', async () => { @@ -244,7 +264,7 @@ describe('OIDCAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('fails if session state is not presented.', async () => { @@ -258,16 +278,19 @@ describe('OIDCAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('fails if authentication response is not valid.', async () => { const { request, attempt, expectedRedirectURI } = getMocks(); - const failureReason = new Error( - 'Failed to exchange code for Id Token using the Token Endpoint.' + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ + statusCode: 400, + body: { message: 'Failed to exchange code for Id Token using the Token Endpoint.' }, + }) ); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason); await expect( provider.login(request, attempt, { @@ -278,17 +301,17 @@ describe('OIDCAuthenticationProvider', () => { }) ).resolves.toEqual(AuthenticationResult.failed(failureReason)); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.oidcAuthenticate', - { - body: { - state: 'statevalue', - nonce: 'noncevalue', - redirect_uri: expectedRedirectURI, - realm: 'oidc1', - }, - } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/oidc/authenticate', + body: { + state: 'statevalue', + nonce: 'noncevalue', + redirect_uri: expectedRedirectURI, + realm: 'oidc1', + }, + }); }); it('fails if realm from state is different from the realm provider is configured with.', async () => { @@ -302,7 +325,7 @@ describe('OIDCAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); } @@ -353,11 +376,6 @@ describe('OIDCAuthenticationProvider', () => { it('redirects non-AJAX request that can not be authenticated to the "capture URL" page.', async () => { const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - id: 'some-request-id', - redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20', - }); - await expect(provider.authenticate(request, null)).resolves.toEqual( AuthenticationResult.redirectTo( '/mock-server-basepath/internal/security/capture-url?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path&providerType=oidc&providerName=oidc', @@ -365,7 +383,7 @@ describe('OIDCAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('succeeds if state contains a valid token.', async () => { @@ -425,8 +443,10 @@ describe('OIDCAuthenticationProvider', () => { }; const authorization = `Bearer ${tokenPair.accessToken}`; - const failureReason = new Error('Token is not valid!'); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 400, body: {} }) + ); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue(failureReason); await expect( provider.authenticate(request, { ...tokenPair, realm: 'oidc1' }) @@ -441,8 +461,8 @@ describe('OIDCAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'expired-token', refreshToken: 'valid-refresh-token' }; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.tokens.refresh.mockResolvedValue({ @@ -475,8 +495,8 @@ describe('OIDCAuthenticationProvider', () => { const tokenPair = { accessToken: 'expired-token', refreshToken: 'invalid-refresh-token' }; const authorization = `Bearer ${tokenPair.accessToken}`; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); const refreshFailureReason = { @@ -502,8 +522,8 @@ describe('OIDCAuthenticationProvider', () => { const tokenPair = { accessToken: 'expired-token', refreshToken: 'expired-refresh-token' }; const authorization = `Bearer ${tokenPair.accessToken}`; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.tokens.refresh.mockResolvedValue(null); @@ -524,10 +544,9 @@ describe('OIDCAuthenticationProvider', () => { expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ headers: { authorization }, }); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); + expect(mockScopedClusterClient.asCurrentUser.security.authenticate).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('fails for AJAX requests with user friendly message if refresh token is expired.', async () => { @@ -535,8 +554,8 @@ describe('OIDCAuthenticationProvider', () => { const tokenPair = { accessToken: 'expired-token', refreshToken: 'expired-refresh-token' }; const authorization = `Bearer ${tokenPair.accessToken}`; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.tokens.refresh.mockResolvedValue(null); @@ -562,8 +581,8 @@ describe('OIDCAuthenticationProvider', () => { const tokenPair = { accessToken: 'expired-token', refreshToken: 'expired-refresh-token' }; const authorization = `Bearer ${tokenPair.accessToken}`; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.tokens.refresh.mockResolvedValue(null); @@ -604,7 +623,7 @@ describe('OIDCAuthenticationProvider', () => { await expect(provider.logout(request)).resolves.toEqual(DeauthenticationResult.notHandled()); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('redirects to logged out view if state is `null` or does not include access token.', async () => { @@ -617,7 +636,7 @@ describe('OIDCAuthenticationProvider', () => { DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('fails if OpenID Connect logout call fails.', async () => { @@ -625,15 +644,22 @@ describe('OIDCAuthenticationProvider', () => { const accessToken = 'x-oidc-token'; const refreshToken = 'x-oidc-refresh-token'; - const failureReason = new Error('Realm is misconfigured!'); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ + statusCode: 400, + body: { message: 'Realm is misconfigured!' }, + }) + ); + mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason); await expect( provider.logout(request, { accessToken, refreshToken, realm: 'oidc1' }) ).resolves.toEqual(DeauthenticationResult.failed(failureReason)); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcLogout', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/oidc/logout', body: { token: accessToken, refresh_token: refreshToken }, }); }); @@ -643,14 +669,18 @@ describe('OIDCAuthenticationProvider', () => { const accessToken = 'x-oidc-token'; const refreshToken = 'x-oidc-refresh-token'; - mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ body: { redirect: null } }) + ); await expect( provider.logout(request, { accessToken, refreshToken, realm: 'oidc1' }) ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcLogout', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/oidc/logout', body: { token: accessToken, refresh_token: refreshToken }, }); }); @@ -660,9 +690,11 @@ describe('OIDCAuthenticationProvider', () => { const accessToken = 'x-oidc-token'; const refreshToken = 'x-oidc-refresh-token'; - mockOptions.client.callAsInternalUser.mockResolvedValue({ - redirect: 'http://fake-idp/logout&id_token_hint=thehint', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { redirect: 'http://fake-idp/logout&id_token_hint=thehint' }, + }) + ); await expect( provider.logout(request, { accessToken, refreshToken, realm: 'oidc1' }) @@ -670,8 +702,10 @@ describe('OIDCAuthenticationProvider', () => { DeauthenticationResult.redirectTo('http://fake-idp/logout&id_token_hint=thehint') ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcLogout', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/oidc/logout', body: { token: accessToken, refresh_token: refreshToken }, }); }); diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts index c46ea37f144e9..b89267f44eeeb 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts @@ -248,14 +248,20 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { try { // This operation should be performed on behalf of the user with a privilege that normal // user usually doesn't have `cluster:admin/xpack/security/oidc/authenticate`. - result = await this.options.client.callAsInternalUser('shield.oidcAuthenticate', { - body: { - state: stateOIDCState, - nonce: stateNonce, - redirect_uri: authenticationResponseURI, - realm: this.realm, - }, - }); + // We can replace generic `transport.request` with a dedicated API method call once + // https://github.com/elastic/elasticsearch/issues/67189 is resolved. + result = ( + await this.options.client.asInternalUser.transport.request({ + method: 'POST', + path: '/_security/oidc/authenticate', + body: { + state: stateOIDCState, + nonce: stateNonce, + redirect_uri: authenticationResponseURI, + realm: this.realm, + }, + }) + ).body as any; } catch (err) { this.logger.debug(`Failed to authenticate request via OpenID Connect: ${err.message}`); return AuthenticationResult.failed(err); @@ -289,11 +295,15 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { try { // This operation should be performed on behalf of the user with a privilege that normal // user usually doesn't have `cluster:admin/xpack/security/oidc/prepare`. - const { - state, - nonce, - redirect, - } = await this.options.client.callAsInternalUser('shield.oidcPrepare', { body: params }); + // We can replace generic `transport.request` with a dedicated API method call once + // https://github.com/elastic/elasticsearch/issues/67189 is resolved. + const { state, nonce, redirect } = ( + await this.options.client.asInternalUser.transport.request({ + method: 'POST', + path: '/_security/oidc/prepare', + body: params, + }) + ).body as any; this.logger.debug('Redirecting to OpenID Connect Provider with authentication request.'); return AuthenticationResult.redirectTo( @@ -407,18 +417,17 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { if (state?.accessToken) { try { - const logoutBody = { - body: { - token: state.accessToken, - refresh_token: state.refreshToken, - }, - }; // This operation should be performed on behalf of the user with a privilege that normal // user usually doesn't have `cluster:admin/xpack/security/oidc/logout`. - const { redirect } = await this.options.client.callAsInternalUser( - 'shield.oidcLogout', - logoutBody - ); + // We can replace generic `transport.request` with a dedicated API method call once + // https://github.com/elastic/elasticsearch/issues/67189 is resolved. + const { redirect } = ( + await this.options.client.asInternalUser.transport.request({ + method: 'POST', + path: '/_security/oidc/logout', + body: { token: state.accessToken, refresh_token: state.refreshToken }, + }) + ).body as any; this.logger.debug('User session has been successfully invalidated.'); diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index 88753f8dc2ab1..d98d6ca4fa071 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -10,18 +10,14 @@ jest.mock('tls'); import { Socket } from 'net'; import { PeerCertificate, TLSSocket } from 'tls'; import Boom from '@hapi/boom'; -import { errors } from 'elasticsearch'; +import { errors } from '@elastic/elasticsearch'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../mocks'; import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock'; -import { - LegacyElasticsearchErrorHelpers, - ILegacyClusterClient, - KibanaRequest, - ScopeableRequest, -} from '../../../../../../src/core/server'; +import { KibanaRequest, ScopeableRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { PKIAuthenticationProvider } from './pki'; @@ -87,15 +83,14 @@ function getMockSocket({ } function expectAuthenticateCall( - mockClusterClient: jest.Mocked, + mockClusterClient: ReturnType, scopeableRequest: ScopeableRequest ) { expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest); const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value; - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); + expect(mockScopedClusterClient.asCurrentUser.security.authenticate).toHaveBeenCalledTimes(1); } describe('PKIAuthenticationProvider', () => { @@ -125,7 +120,7 @@ describe('PKIAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual(AuthenticationResult.notHandled()); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); expectDebugLogs( 'Peer certificate chain: [{"subject":"mock subject(2A:7A:C2:DD)","issuer":"mock issuer","issuerCertType":"object","subjectaltname":"mock subjectaltname","validFrom":"mock valid_from","validTo":"mock valid_to"}]', 'Authentication is not possible since peer certificate was not authorized: Error: mock authorization error.' @@ -139,7 +134,7 @@ describe('PKIAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual(AuthenticationResult.notHandled()); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); expectDebugLogs( 'Peer certificate chain: []', 'Authentication is not possible due to missing peer certificate chain.' @@ -159,7 +154,7 @@ describe('PKIAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual(AuthenticationResult.notHandled()); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); expectDebugLogs( `Detected incomplete certificate chain with protocol 'TLSv1.3', cannot renegotiate connection.`, 'Peer certificate chain: [{"subject":"mock subject(2A:7A:C2:DD)","issuer":"mock issuer","issuerCertType":"undefined","subjectaltname":"mock subjectaltname","validFrom":"mock valid_from","validTo":"mock valid_to"}]', @@ -181,7 +176,7 @@ describe('PKIAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual(AuthenticationResult.notHandled()); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); expectDebugLogs( `Detected incomplete certificate chain with protocol 'TLSv1.2', attempting to renegotiate connection.`, `Failed to renegotiate connection: Error: Oh no!.`, @@ -203,7 +198,7 @@ describe('PKIAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual(AuthenticationResult.notHandled()); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); expectDebugLogs( `Detected incomplete certificate chain with protocol 'TLSv1.2', attempting to renegotiate connection.`, 'Peer certificate chain: [{"subject":"mock subject(2A:7A:C2:DD)","issuer":"mock issuer","issuerCertType":"undefined","subjectaltname":"mock subjectaltname","validFrom":"mock valid_from","validTo":"mock valid_to"}]', @@ -231,7 +226,7 @@ describe('PKIAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual(AuthenticationResult.notHandled()); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); expectDebugLogs( `Detected incomplete certificate chain with protocol 'TLSv1.2', attempting to renegotiate connection.`, 'Self-signed certificate is detected in certificate chain', @@ -253,10 +248,11 @@ describe('PKIAuthenticationProvider', () => { mockGetPeerCertificate.mockReturnValueOnce(peerCertificate1); const request = httpServerMock.createKibanaRequest({ socket }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - authentication: user, - access_token: 'access-token', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { authentication: user, access_token: 'access-token' }, + }) + ); await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( @@ -268,8 +264,10 @@ describe('PKIAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/delegate_pki', body: { x509_certificate_chain: [ 'fingerprint:2A:7A:C2:DD:base64', @@ -295,10 +293,11 @@ describe('PKIAuthenticationProvider', () => { const { socket } = getMockSocket({ authorized: true, peerCertificate }); const request = httpServerMock.createKibanaRequest({ socket, headers: {} }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - authentication: user, - access_token: 'access-token', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { authentication: user, access_token: 'access-token' }, + }) + ); await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( @@ -310,8 +309,10 @@ describe('PKIAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/delegate_pki', body: { x509_certificate_chain: [ 'fingerprint:2A:7A:C2:DD:base64', @@ -330,10 +331,11 @@ describe('PKIAuthenticationProvider', () => { const { socket } = getMockSocket({ authorized: true, peerCertificate }); const request = httpServerMock.createKibanaRequest({ socket, headers: {} }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - authentication: user, - access_token: 'access-token', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { authentication: user, access_token: 'access-token' }, + }) + ); await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( @@ -345,8 +347,10 @@ describe('PKIAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/delegate_pki', body: { x509_certificate_chain: ['fingerprint:2A:7A:C2:DD:base64'] }, }); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); @@ -359,13 +363,17 @@ describe('PKIAuthenticationProvider', () => { const { socket } = getMockSocket({ authorized: true, peerCertificate }); const request = httpServerMock.createKibanaRequest({ socket, headers: {} }); - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 401, body: {} }) + ); + mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason); await expect(operation(request)).resolves.toEqual(AuthenticationResult.failed(failureReason)); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/delegate_pki', body: { x509_certificate_chain: ['fingerprint:2A:7A:C2:DD:base64'] }, }); @@ -390,7 +398,7 @@ describe('PKIAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); expect(request.headers.authorization).toBe('Bearer some-token'); }); @@ -408,7 +416,7 @@ describe('PKIAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); expect(request.headers.authorization).toBe('Bearer some-token'); }); @@ -421,7 +429,7 @@ describe('PKIAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('does not exchange peer certificate to access token for Ajax requests.', async () => { @@ -436,7 +444,7 @@ describe('PKIAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('fails with non-401 error if state is available, peer is authorized, but certificate is not available.', async () => { @@ -490,10 +498,11 @@ describe('PKIAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest({ socket }); const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '3A:9A:C5:DD' }; - mockOptions.client.callAsInternalUser.mockResolvedValue({ - authentication: user, - access_token: 'access-token', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { authentication: user, access_token: 'access-token' }, + }) + ); await expect(provider.authenticate(request, state)).resolves.toEqual( AuthenticationResult.succeeded( @@ -510,8 +519,10 @@ describe('PKIAuthenticationProvider', () => { accessToken: state.accessToken, }); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/delegate_pki', body: { x509_certificate_chain: [ 'fingerprint:2A:7A:C2:DD:base64', @@ -526,15 +537,16 @@ describe('PKIAuthenticationProvider', () => { it('gets a new access token even if existing token is expired.', async () => { const user = mockAuthenticatedUser({ authentication_provider: { type: 'pki', name: 'pki' } }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - authentication: user, - access_token: 'access-token', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { authentication: user, access_token: 'access-token' }, + }) + ); const nonAjaxRequest = httpServerMock.createKibanaRequest({ socket: getMockSocket({ @@ -589,8 +601,10 @@ describe('PKIAuthenticationProvider', () => { }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(3); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(3); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/delegate_pki', body: { x509_certificate_chain: [ 'fingerprint:2A:7A:C2:DD:base64', @@ -598,7 +612,9 @@ describe('PKIAuthenticationProvider', () => { ], }, }); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/delegate_pki', body: { x509_certificate_chain: [ 'fingerprint:3A:7A:C2:DD:base64', @@ -606,7 +622,9 @@ describe('PKIAuthenticationProvider', () => { ], }, }); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/delegate_pki', body: { x509_certificate_chain: [ 'fingerprint:4A:7A:C2:DD:base64', @@ -625,9 +643,9 @@ describe('PKIAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest({ socket }); const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); @@ -635,7 +653,7 @@ describe('PKIAuthenticationProvider', () => { AuthenticationResult.failed(Boom.unauthorized()) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); expect(request.headers).not.toHaveProperty('authorization'); }); @@ -647,8 +665,10 @@ describe('PKIAuthenticationProvider', () => { const { socket } = getMockSocket({ authorized: true, peerCertificate }); const request = httpServerMock.createKibanaRequest({ socket, headers: {} }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, state)).resolves.toEqual( @@ -660,7 +680,7 @@ describe('PKIAuthenticationProvider', () => { expectAuthenticateCall(mockOptions.client, { headers: { authorization: 'Bearer token' } }); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); expect(request.headers).not.toHaveProperty('authorization'); }); @@ -671,9 +691,12 @@ describe('PKIAuthenticationProvider', () => { const { socket } = getMockSocket({ authorized: true, peerCertificate }); const request = httpServerMock.createKibanaRequest({ socket, headers: {} }); - const failureReason = new errors.ServiceUnavailable(); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + const failureReason = new errors.ConnectionError( + 'unavailable', + securityMock.createApiResponse({ statusCode: 503, body: {} }) + ); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue(failureReason); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, state)).resolves.toEqual( diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index 3171c5ff24abe..a5dc870bf9c84 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -272,9 +272,15 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { let result: { access_token: string; authentication: AuthenticationInfo }; try { - result = await this.options.client.callAsInternalUser('shield.delegatePKI', { - body: { x509_certificate_chain: certificateChain }, - }); + // We can replace generic `transport.request` with a dedicated API method call once + // https://github.com/elastic/elasticsearch/issues/67189 is resolved. + result = ( + await this.options.client.asInternalUser.transport.request({ + method: 'POST', + path: '/_security/delegate_pki', + body: { x509_certificate_chain: certificateChain }, + }) + ).body as any; } catch (err) { this.logger.debug( `Failed to exchange peer certificate chain to an access token: ${err.message}` @@ -298,7 +304,8 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { } /** - * Obtains the peer certificate chain. Starts from the leaf peer certificate and iterates up to the top-most available certificate + * Obtains the peer certificate chain as an ordered array of base64-encoded (Section 4 of RFC4648 - not base64url-encoded) + * DER PKIX certificate values. Starts from the leaf peer certificate and iterates up to the top-most available certificate * authority using `issuerCertificate` certificate property. THe iteration is stopped only when we detect circular reference * (root/self-signed certificate) or when `issuerCertificate` isn't available (null or empty object). Automatically attempts to * renegotiate the TLS connection once if the peer certificate chain is incomplete. diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index 5cba017e4916b..48334358bb001 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -5,15 +5,13 @@ */ import Boom from '@hapi/boom'; +import { errors } from '@elastic/elasticsearch'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../mocks'; import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock'; -import { - LegacyElasticsearchErrorHelpers, - ILegacyScopedClusterClient, -} from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { SAMLAuthenticationProvider, SAMLLogin } from './saml'; @@ -23,19 +21,17 @@ describe('SAMLAuthenticationProvider', () => { let provider: SAMLAuthenticationProvider; let mockOptions: MockAuthenticationProviderOptions; let mockUser: AuthenticatedUser; - let mockScopedClusterClient: jest.Mocked; + let mockScopedClusterClient: ReturnType< + typeof elasticsearchServiceMock.createScopedClusterClient + >; beforeEach(() => { mockOptions = mockAuthenticationProviderOptions({ name: 'saml' }); - mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); mockUser = mockAuthenticatedUser({ authentication_provider: { type: 'saml', name: 'saml' } }); - mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method) => { - if (method === 'shield.authenticate') { - return mockUser; - } - - throw new Error(`Unexpected call to ${method}!`); - }); + mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: mockUser }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); provider = new SAMLAuthenticationProvider(mockOptions, { @@ -61,11 +57,15 @@ describe('SAMLAuthenticationProvider', () => { it('gets token and redirects user to requested URL if SAML Response is valid.', async () => { const request = httpServerMock.createKibanaRequest(); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - access_token: 'some-token', - refresh_token: 'some-refresh-token', - authentication: mockUser, - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + access_token: 'some-token', + refresh_token: 'some-refresh-token', + authentication: mockUser, + }, + }) + ); await expect( provider.login( @@ -88,20 +88,25 @@ describe('SAMLAuthenticationProvider', () => { }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.samlAuthenticate', - { body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' } } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' }, + }); }); it('gets token and redirects user to the requested URL if SAML Response is valid ignoring Relay State.', async () => { const request = httpServerMock.createKibanaRequest(); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - access_token: 'some-token', - refresh_token: 'some-refresh-token', - authentication: mockUser, - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + access_token: 'some-token', + refresh_token: 'some-refresh-token', + authentication: mockUser, + }, + }) + ); provider = new SAMLAuthenticationProvider(mockOptions, { realm: 'test-realm', @@ -132,10 +137,11 @@ describe('SAMLAuthenticationProvider', () => { }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.samlAuthenticate', - { body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' } } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' }, + }); }); it('fails if SAML Response payload is presented but state does not contain SAML Request token.', async () => { @@ -153,7 +159,7 @@ describe('SAMLAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('fails if realm from state is different from the realm provider is configured with.', async () => { @@ -173,17 +179,21 @@ describe('SAMLAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('redirects to the default location if state contains empty redirect URL.', async () => { const request = httpServerMock.createKibanaRequest(); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - access_token: 'user-initiated-login-token', - refresh_token: 'user-initiated-login-refresh-token', - authentication: mockUser, - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + access_token: 'user-initiated-login-token', + refresh_token: 'user-initiated-login-refresh-token', + authentication: mockUser, + }, + }) + ); await expect( provider.login( @@ -202,20 +212,25 @@ describe('SAMLAuthenticationProvider', () => { }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.samlAuthenticate', - { body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' } } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' }, + }); }); it('redirects to the default location if state contains empty redirect URL ignoring Relay State.', async () => { const request = httpServerMock.createKibanaRequest(); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - access_token: 'user-initiated-login-token', - refresh_token: 'user-initiated-login-refresh-token', - authentication: mockUser, - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + access_token: 'user-initiated-login-token', + refresh_token: 'user-initiated-login-refresh-token', + authentication: mockUser, + }, + }) + ); provider = new SAMLAuthenticationProvider(mockOptions, { realm: 'test-realm', @@ -242,20 +257,25 @@ describe('SAMLAuthenticationProvider', () => { }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.samlAuthenticate', - { body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' } } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' }, + }); }); it('redirects to the default location if state is not presented.', async () => { const request = httpServerMock.createKibanaRequest(); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - access_token: 'idp-initiated-login-token', - refresh_token: 'idp-initiated-login-refresh-token', - authentication: mockUser, - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + access_token: 'idp-initiated-login-token', + refresh_token: 'idp-initiated-login-refresh-token', + authentication: mockUser, + }, + }) + ); await expect( provider.login(request, { @@ -273,17 +293,20 @@ describe('SAMLAuthenticationProvider', () => { }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.samlAuthenticate', - { body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' } } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' }, + }); }); it('fails if SAML Response is rejected.', async () => { const request = httpServerMock.createKibanaRequest(); - const failureReason = new Error('SAML response is stale!'); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 503, body: {} }) + ); + mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason); await expect( provider.login( @@ -297,22 +320,27 @@ describe('SAMLAuthenticationProvider', () => { ) ).resolves.toEqual(AuthenticationResult.failed(failureReason)); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.samlAuthenticate', - { body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' } } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' }, + }); }); describe('IdP initiated login', () => { beforeEach(() => { mockOptions.basePath.get.mockReturnValue(mockOptions.basePath.serverBasePath); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - username: 'user', - access_token: 'valid-token', - refresh_token: 'valid-refresh-token', - authentication: mockUser, - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + username: 'user', + access_token: 'valid-token', + refresh_token: 'valid-refresh-token', + authentication: mockUser, + }, + }) + ); provider = new SAMLAuthenticationProvider(mockOptions, { realm: 'test-realm', @@ -428,8 +456,10 @@ describe('SAMLAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); const authorization = 'Bearer some-valid-token'; - const failureReason = new Error('SAML response is invalid!'); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 503, body: {} }) + ); + mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason); await expect( provider.login( @@ -444,12 +474,11 @@ describe('SAMLAuthenticationProvider', () => { ).resolves.toEqual(AuthenticationResult.notHandled()); expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ headers: { authorization } }); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.samlAuthenticate', - { - body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' }, - } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' }, + }); }); it('fails if fails to invalidate existing access/refresh tokens.', async () => { @@ -461,12 +490,16 @@ describe('SAMLAuthenticationProvider', () => { }; const authorization = `Bearer ${state.accessToken}`; - mockOptions.client.callAsInternalUser.mockResolvedValue({ - username: 'user', - access_token: 'new-valid-token', - refresh_token: 'new-valid-refresh-token', - authentication: mockUser, - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + username: 'user', + access_token: 'new-valid-token', + refresh_token: 'new-valid-refresh-token', + authentication: mockUser, + }, + }) + ); const failureReason = new Error('Failed to invalidate token!'); mockOptions.tokens.invalidate.mockRejectedValue(failureReason); @@ -480,12 +513,11 @@ describe('SAMLAuthenticationProvider', () => { ).resolves.toEqual(AuthenticationResult.failed(failureReason)); expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ headers: { authorization } }); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.samlAuthenticate', - { - body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' }, - } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' }, + }); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ @@ -498,14 +530,20 @@ describe('SAMLAuthenticationProvider', () => { [ 'current session is valid', Promise.resolve( - mockAuthenticatedUser({ authentication_provider: { type: 'saml', name: 'saml' } }) + securityMock.createApiResponse({ + body: mockAuthenticatedUser({ + authentication_provider: { type: 'saml', name: 'saml' }, + }), + }) ), ], [ 'current session is is expired', - Promise.reject(LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())), + Promise.reject( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) + ), ], - ] as Array<[string, Promise]>) { + ] as Array<[string, any]>) { it(`redirects to the home page if ${description}.`, async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); const state = { @@ -516,19 +554,19 @@ describe('SAMLAuthenticationProvider', () => { const authorization = `Bearer ${state.accessToken}`; // The first call is made using tokens from existing session. - mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(() => response); - // The second call is made using new tokens. - mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(() => - Promise.resolve(mockUser) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockImplementationOnce( + () => response + ); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + username: 'user', + access_token: 'new-valid-token', + refresh_token: 'new-valid-refresh-token', + authentication: mockUser, + }, + }) ); - - mockOptions.client.callAsInternalUser.mockResolvedValue({ - username: 'user', - access_token: 'new-valid-token', - refresh_token: 'new-valid-refresh-token', - authentication: mockUser, - }); - mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect( @@ -549,12 +587,11 @@ describe('SAMLAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ headers: { authorization } }); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.samlAuthenticate', - { - body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' }, - } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' }, + }); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ @@ -573,13 +610,19 @@ describe('SAMLAuthenticationProvider', () => { const authorization = `Bearer ${state.accessToken}`; // The first call is made using tokens from existing session. - mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(() => response); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - username: 'user', - access_token: 'new-valid-token', - refresh_token: 'new-valid-refresh-token', - authentication: mockUser, - }); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockImplementationOnce( + () => response + ); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + username: 'user', + access_token: 'new-valid-token', + refresh_token: 'new-valid-refresh-token', + authentication: mockUser, + }, + }) + ); mockOptions.tokens.invalidate.mockResolvedValue(undefined); @@ -610,12 +653,11 @@ describe('SAMLAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ headers: { authorization } }); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith( - 'shield.samlAuthenticate', - { - body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' }, - } - ); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' }, + }); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ @@ -641,16 +683,20 @@ describe('SAMLAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('redirects requests to the IdP remembering redirect URL with existing state.', async () => { const request = httpServerMock.createKibanaRequest(); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - id: 'some-request-id', - redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + id: 'some-request-id', + redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20', + }, + }) + ); await expect( provider.login( @@ -674,7 +720,9 @@ describe('SAMLAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlPrepare', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/prepare', body: { realm: 'test-realm' }, }); @@ -684,10 +732,14 @@ describe('SAMLAuthenticationProvider', () => { it('redirects requests to the IdP remembering redirect URL without state.', async () => { const request = httpServerMock.createKibanaRequest(); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - id: 'some-request-id', - redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + id: 'some-request-id', + redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20', + }, + }) + ); await expect( provider.login( @@ -711,7 +763,9 @@ describe('SAMLAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlPrepare', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/prepare', body: { realm: 'test-realm' }, }); @@ -721,8 +775,10 @@ describe('SAMLAuthenticationProvider', () => { it('fails if SAML request preparation fails.', async () => { const request = httpServerMock.createKibanaRequest(); - const failureReason = new Error('Realm is misconfigured!'); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 401, body: {} }) + ); + mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason); await expect( provider.login( @@ -735,7 +791,9 @@ describe('SAMLAuthenticationProvider', () => { ) ).resolves.toEqual(AuthenticationResult.failed(failureReason)); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlPrepare', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/prepare', body: { realm: 'test-realm' }, }); }); @@ -791,11 +849,6 @@ describe('SAMLAuthenticationProvider', () => { it('redirects non-AJAX request that can not be authenticated to the "capture URL" page.', async () => { const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - id: 'some-request-id', - redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20', - }); - await expect(provider.authenticate(request)).resolves.toEqual( AuthenticationResult.redirectTo( '/mock-server-basepath/internal/security/capture-url?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path&providerType=saml&providerName=saml', @@ -803,7 +856,7 @@ describe('SAMLAuthenticationProvider', () => { ) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('succeeds if state contains a valid token.', async () => { @@ -833,11 +886,13 @@ describe('SAMLAuthenticationProvider', () => { }; const authorization = `Bearer ${state.accessToken}`; - const failureReason = { statusCode: 500, message: 'Token is not valid!' }; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 500, body: {} }) + ); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue(failureReason); await expect(provider.authenticate(request, state)).resolves.toEqual( - AuthenticationResult.failed(failureReason as any) + AuthenticationResult.failed(failureReason) ); expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ headers: { authorization } }); @@ -853,8 +908,8 @@ describe('SAMLAuthenticationProvider', () => { realm: 'test-realm', }; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.tokens.refresh.mockResolvedValue({ @@ -889,8 +944,8 @@ describe('SAMLAuthenticationProvider', () => { }; const authorization = `Bearer ${state.accessToken}`; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); const refreshFailureReason = { @@ -920,8 +975,8 @@ describe('SAMLAuthenticationProvider', () => { }; const authorization = `Bearer ${state.accessToken}`; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.tokens.refresh.mockResolvedValue(null); @@ -949,8 +1004,8 @@ describe('SAMLAuthenticationProvider', () => { }; const authorization = `Bearer ${state.accessToken}`; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.tokens.refresh.mockResolvedValue(null); @@ -976,8 +1031,8 @@ describe('SAMLAuthenticationProvider', () => { }; const authorization = `Bearer ${state.accessToken}`; - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.tokens.refresh.mockResolvedValue(null); @@ -994,7 +1049,7 @@ describe('SAMLAuthenticationProvider', () => { expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ headers: { authorization } }); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('fails if realm from state is different from the realm provider is configured with.', async () => { @@ -1015,7 +1070,7 @@ describe('SAMLAuthenticationProvider', () => { await expect(provider.logout(request)).resolves.toEqual(DeauthenticationResult.notHandled()); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('redirects to logged out view if state is `null` or does not include access token.', async () => { @@ -1028,7 +1083,7 @@ describe('SAMLAuthenticationProvider', () => { DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('fails if SAML logout call fails.', async () => { @@ -1036,8 +1091,10 @@ describe('SAMLAuthenticationProvider', () => { const accessToken = 'x-saml-token'; const refreshToken = 'x-saml-refresh-token'; - const failureReason = new Error('Realm is misconfigured!'); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 500, body: {} }) + ); + mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason); await expect( provider.logout(request, { @@ -1047,8 +1104,10 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual(DeauthenticationResult.failed(failureReason)); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/logout', body: { token: accessToken, refresh_token: refreshToken }, }); }); @@ -1056,15 +1115,19 @@ describe('SAMLAuthenticationProvider', () => { it('fails if SAML invalidate call fails.', async () => { const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } }); - const failureReason = new Error('Realm is misconfigured!'); - mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 500, body: {} }) + ); + mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason); await expect(provider.logout(request)).resolves.toEqual( DeauthenticationResult.failed(failureReason) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/invalidate', body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' }, }); }); @@ -1074,7 +1137,9 @@ describe('SAMLAuthenticationProvider', () => { const accessToken = 'x-saml-token'; const refreshToken = 'x-saml-refresh-token'; - mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ body: { redirect: null } }) + ); await expect( provider.logout(request, { @@ -1084,8 +1149,10 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/logout', body: { token: accessToken, refresh_token: refreshToken }, }); }); @@ -1095,7 +1162,9 @@ describe('SAMLAuthenticationProvider', () => { const accessToken = 'x-saml-token'; const refreshToken = 'x-saml-refresh-token'; - mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: undefined }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ body: { redirect: undefined } }) + ); await expect( provider.logout(request, { @@ -1105,8 +1174,10 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/logout', body: { token: accessToken, refresh_token: refreshToken }, }); }); @@ -1118,7 +1189,9 @@ describe('SAMLAuthenticationProvider', () => { const accessToken = 'x-saml-token'; const refreshToken = 'x-saml-refresh-token'; - mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ body: { redirect: null } }) + ); await expect( provider.logout(request, { @@ -1128,8 +1201,10 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/logout', body: { token: accessToken, refresh_token: refreshToken }, }); }); @@ -1137,7 +1212,9 @@ describe('SAMLAuthenticationProvider', () => { it('relies on SAML invalidate call even if access token is presented.', async () => { const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ body: { redirect: null } }) + ); await expect( provider.logout(request, { @@ -1147,8 +1224,10 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/invalidate', body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' }, }); }); @@ -1156,14 +1235,18 @@ describe('SAMLAuthenticationProvider', () => { it('redirects to `loggedOut` URL if `redirect` field in SAML invalidate response is null.', async () => { const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ body: { redirect: null } }) + ); await expect(provider.logout(request)).resolves.toEqual( DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/invalidate', body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' }, }); }); @@ -1171,14 +1254,18 @@ describe('SAMLAuthenticationProvider', () => { it('redirects to `loggedOut` URL if `redirect` field in SAML invalidate response is not defined.', async () => { const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: undefined }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ body: { redirect: undefined } }) + ); await expect(provider.logout(request)).resolves.toEqual( DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', { + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/invalidate', body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' }, }); }); @@ -1190,7 +1277,7 @@ describe('SAMLAuthenticationProvider', () => { DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); it('redirects user to the IdP if SLO is supported by IdP in case of SP initiated logout.', async () => { @@ -1198,9 +1285,11 @@ describe('SAMLAuthenticationProvider', () => { const accessToken = 'x-saml-token'; const refreshToken = 'x-saml-refresh-token'; - mockOptions.client.callAsInternalUser.mockResolvedValue({ - redirect: 'http://fake-idp/SLO?SAMLRequest=7zlH37H', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { redirect: 'http://fake-idp/SLO?SAMLRequest=7zlH37H' }, + }) + ); await expect( provider.logout(request, { @@ -1212,15 +1301,17 @@ describe('SAMLAuthenticationProvider', () => { DeauthenticationResult.redirectTo('http://fake-idp/SLO?SAMLRequest=7zlH37H') ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); }); it('redirects user to the IdP if SLO is supported by IdP in case of IdP initiated logout.', async () => { const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } }); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - redirect: 'http://fake-idp/SLO?SAMLRequest=7zlH37H', - }); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { redirect: 'http://fake-idp/SLO?SAMLRequest=7zlH37H' }, + }) + ); await expect( provider.logout(request, { @@ -1232,7 +1323,7 @@ describe('SAMLAuthenticationProvider', () => { DeauthenticationResult.redirectTo('http://fake-idp/SLO?SAMLRequest=7zlH37H') ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); }); }); diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 34639a849d354..58792727de733 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -343,13 +343,19 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { try { // This operation should be performed on behalf of the user with a privilege that normal // user usually doesn't have `cluster:admin/xpack/security/saml/authenticate`. - result = await this.options.client.callAsInternalUser('shield.samlAuthenticate', { - body: { - ids: !isIdPInitiatedLogin ? [stateRequestId] : [], - content: samlResponse, - realm: this.realm, - }, - }); + // We can replace generic `transport.request` with a dedicated API method call once + // https://github.com/elastic/elasticsearch/issues/67189 is resolved. + result = ( + await this.options.client.asInternalUser.transport.request({ + method: 'POST', + path: '/_security/saml/authenticate', + body: { + ids: !isIdPInitiatedLogin ? [stateRequestId] : [], + content: samlResponse, + realm: this.realm, + }, + }) + ).body as any; } catch (err) { this.logger.debug(`Failed to log in with SAML response: ${err.message}`); @@ -541,12 +547,15 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { try { // This operation should be performed on behalf of the user with a privilege that normal // user usually doesn't have `cluster:admin/xpack/security/saml/prepare`. - const { id: requestId, redirect } = await this.options.client.callAsInternalUser( - 'shield.samlPrepare', - { + // We can replace generic `transport.request` with a dedicated API method call once + // https://github.com/elastic/elasticsearch/issues/67189 is resolved. + const { id: requestId, redirect } = ( + await this.options.client.asInternalUser.transport.request({ + method: 'POST', + path: '/_security/saml/prepare', body: { realm: this.realm }, - } - ); + }) + ).body as any; this.logger.debug('Redirecting to Identity Provider with SAML request.'); @@ -570,9 +579,15 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { // This operation should be performed on behalf of the user with a privilege that normal // user usually doesn't have `cluster:admin/xpack/security/saml/logout`. - const { redirect } = await this.options.client.callAsInternalUser('shield.samlLogout', { - body: { token: accessToken, refresh_token: refreshToken }, - }); + // We can replace generic `transport.request` with a dedicated API method call once + // https://github.com/elastic/elasticsearch/issues/67189 is resolved. + const { redirect } = ( + await this.options.client.asInternalUser.transport.request({ + method: 'POST', + path: '/_security/saml/logout', + body: { token: accessToken, refresh_token: refreshToken }, + }) + ).body as any; this.logger.debug('User session has been successfully invalidated.'); @@ -589,13 +604,19 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { // This operation should be performed on behalf of the user with a privilege that normal // user usually doesn't have `cluster:admin/xpack/security/saml/invalidate`. - const { redirect } = await this.options.client.callAsInternalUser('shield.samlInvalidate', { - // Elasticsearch expects `queryString` without leading `?`, so we should strip it with `slice`. - body: { - queryString: request.url.search ? request.url.search.slice(1) : '', - realm: this.realm, - }, - }); + // We can replace generic `transport.request` with a dedicated API method call once + // https://github.com/elastic/elasticsearch/issues/67189 is resolved. + const { redirect } = ( + await this.options.client.asInternalUser.transport.request({ + method: 'POST', + path: '/_security/saml/invalidate', + // Elasticsearch expects `queryString` without leading `?`, so we should strip it with `slice`. + body: { + queryString: request.url.search ? request.url.search.slice(1) : '', + realm: this.realm, + }, + }) + ).body as any; this.logger.debug('User session has been successfully invalidated.'); diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index 5a600461ef467..ad100ac5be893 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -5,31 +5,27 @@ */ import Boom from '@hapi/boom'; -import { errors } from 'elasticsearch'; +import { errors } from '@elastic/elasticsearch'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../mocks'; import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock'; -import { - LegacyElasticsearchErrorHelpers, - ILegacyClusterClient, - ScopeableRequest, -} from '../../../../../../src/core/server'; +import { ScopeableRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { TokenAuthenticationProvider } from './token'; function expectAuthenticateCall( - mockClusterClient: jest.Mocked, + mockClusterClient: ReturnType, scopeableRequest: ScopeableRequest ) { expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest); const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value; - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); + expect(mockScopedClusterClient.asCurrentUser.security.authenticate).toHaveBeenCalledTimes(1); } describe('TokenAuthenticationProvider', () => { @@ -51,11 +47,15 @@ describe('TokenAuthenticationProvider', () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; const authorization = `Bearer ${tokenPair.accessToken}`; - mockOptions.client.callAsInternalUser.mockResolvedValue({ - access_token: tokenPair.accessToken, - refresh_token: tokenPair.refreshToken, - authentication: user, - }); + mockOptions.client.asInternalUser.security.getToken.mockResolvedValue( + securityMock.createApiResponse({ + body: { + access_token: tokenPair.accessToken, + refresh_token: tokenPair.refreshToken, + authentication: user, + }, + }) + ); await expect(provider.login(request, credentials)).resolves.toEqual( AuthenticationResult.succeeded( @@ -65,8 +65,8 @@ describe('TokenAuthenticationProvider', () => { ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { + expect(mockOptions.client.asInternalUser.security.getToken).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.security.getToken).toHaveBeenCalledWith({ body: { grant_type: 'password', ...credentials }, }); }); @@ -75,17 +75,18 @@ describe('TokenAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); const credentials = { username: 'user', password: 'password' }; - const authenticationError = new Error('Invalid credentials'); - mockOptions.client.callAsInternalUser.mockRejectedValue(authenticationError); + const authenticationError = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 500, body: {} }) + ); + mockOptions.client.asInternalUser.security.getToken.mockRejectedValue(authenticationError); await expect(provider.login(request, credentials)).resolves.toEqual( AuthenticationResult.failed(authenticationError) ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { + expect(mockOptions.client.asInternalUser.security.getToken).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.security.getToken).toHaveBeenCalledWith({ body: { grant_type: 'password', ...credentials }, }); @@ -158,8 +159,10 @@ describe('TokenAuthenticationProvider', () => { const user = mockAuthenticatedUser(); const authorization = `Bearer ${tokenPair.accessToken}`; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockResolvedValue( + securityMock.createApiResponse({ body: user }) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( @@ -179,9 +182,9 @@ describe('TokenAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); @@ -212,9 +215,13 @@ describe('TokenAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); const authorization = `Bearer ${tokenPair.accessToken}`; - const authenticationError = new errors.InternalServerError('something went wrong'); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError); + const authenticationError = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 500, body: {} }) + ); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + authenticationError + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( @@ -231,13 +238,15 @@ describe('TokenAuthenticationProvider', () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; const authorization = `Bearer ${tokenPair.accessToken}`; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - const refreshError = new errors.InternalServerError('failed to refresh token'); + const refreshError = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 500, body: {} }) + ); mockOptions.tokens.refresh.mockRejectedValue(refreshError); await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( @@ -257,9 +266,9 @@ describe('TokenAuthenticationProvider', () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; const authorization = `Bearer ${tokenPair.accessToken}`; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); @@ -288,9 +297,9 @@ describe('TokenAuthenticationProvider', () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; const authorization = `Bearer ${tokenPair.accessToken}`; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); @@ -319,9 +328,9 @@ describe('TokenAuthenticationProvider', () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; const authorization = `Bearer ${tokenPair.accessToken}`; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} })) ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts index 67c2d244e75a2..3afbc25122a26 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.ts @@ -7,6 +7,8 @@ import Boom from '@hapi/boom'; import { KibanaRequest } from '../../../../../../src/core/server'; import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; +import { AuthenticationInfo } from '../../elasticsearch'; +import { getDetailedErrorMessage } from '../../errors'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { canRedirectRequest } from '../can_redirect_request'; @@ -65,9 +67,13 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { access_token: accessToken, refresh_token: refreshToken, authentication: authenticationInfo, - } = await this.options.client.callAsInternalUser('shield.getAccessToken', { - body: { grant_type: 'password', username, password }, - }); + } = ( + await this.options.client.asInternalUser.security.getToken<{ + access_token: string; + refresh_token: string; + authentication: AuthenticationInfo; + }>({ body: { grant_type: 'password', username, password } }) + ).body; this.logger.debug('Get token API request to Elasticsearch successful'); return AuthenticationResult.succeeded( @@ -80,7 +86,7 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { } ); } catch (err) { - this.logger.debug(`Failed to perform a login: ${err.message}`); + this.logger.debug(`Failed to perform a login: ${getDetailedErrorMessage(err)}`); return AuthenticationResult.failed(err); } } diff --git a/x-pack/plugins/security/server/authentication/tokens.test.ts b/x-pack/plugins/security/server/authentication/tokens.test.ts index 18fdcf8608d29..eb439d74a46d0 100644 --- a/x-pack/plugins/security/server/authentication/tokens.test.ts +++ b/x-pack/plugins/security/server/authentication/tokens.test.ts @@ -4,25 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { errors } from 'elasticsearch'; +import { errors } from '@elastic/elasticsearch'; +import { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { elasticsearchServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; +import { securityMock } from '../mocks'; -import { - ILegacyClusterClient, - LegacyElasticsearchErrorHelpers, -} from '../../../../../src/core/server'; +import { ElasticsearchClient } from '../../../../../src/core/server'; import { Tokens } from './tokens'; describe('Tokens', () => { let tokens: Tokens; - let mockClusterClient: jest.Mocked; + let mockElasticsearchClient: DeeplyMockedKeys; beforeEach(() => { - mockClusterClient = elasticsearchServiceMock.createLegacyClusterClient(); + mockElasticsearchClient = elasticsearchServiceMock.createElasticsearchClient(); const tokensOptions = { - client: mockClusterClient, + client: mockElasticsearchClient, logger: loggingSystemMock.create().get(), }; @@ -33,9 +32,24 @@ describe('Tokens', () => { const nonExpirationErrors = [ {}, new Error(), - new errors.InternalServerError(), - new errors.Forbidden(), + new errors.NoLivingConnectionsError( + 'Server is not available', + securityMock.createApiResponse({ body: {} }) + ), + new errors.ResponseError( + securityMock.createApiResponse({ + statusCode: 403, + body: { error: { reason: 'forbidden' } }, + }) + ), { statusCode: 500, body: { error: { reason: 'some unknown reason' } } }, + new errors.NoLivingConnectionsError( + 'Server is not available', + securityMock.createApiResponse({ + statusCode: 500, + body: { error: { reason: 'some unknown reason' } }, + }) + ), ]; for (const error of nonExpirationErrors) { expect(Tokens.isAccessTokenExpiredError(error)).toBe(false); @@ -43,8 +57,10 @@ describe('Tokens', () => { const expirationErrors = [ { statusCode: 401 }, - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()), - new errors.AuthenticationException(), + securityMock.createApiResponse({ + statusCode: 401, + body: { error: { reason: 'unauthenticated' } }, + }), ]; for (const error of expirationErrors) { expect(Tokens.isAccessTokenExpiredError(error)).toBe(true); @@ -55,25 +71,30 @@ describe('Tokens', () => { const refreshToken = 'some-refresh-token'; it('throws if API call fails with unknown reason', async () => { - const refreshFailureReason = new errors.ServiceUnavailable('Server is not available'); - mockClusterClient.callAsInternalUser.mockRejectedValue(refreshFailureReason); + const refreshFailureReason = new errors.NoLivingConnectionsError( + 'Server is not available', + securityMock.createApiResponse({ body: {} }) + ); + mockElasticsearchClient.security.getToken.mockRejectedValue(refreshFailureReason); await expect(tokens.refresh(refreshToken)).rejects.toBe(refreshFailureReason); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { + expect(mockElasticsearchClient.security.getToken).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.security.getToken).toHaveBeenCalledWith({ body: { grant_type: 'refresh_token', refresh_token: refreshToken }, }); }); it('returns `null` if refresh token is not valid', async () => { - const refreshFailureReason = new errors.BadRequest(); - mockClusterClient.callAsInternalUser.mockRejectedValue(refreshFailureReason); + const refreshFailureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 400, body: {} }) + ); + mockElasticsearchClient.security.getToken.mockRejectedValue(refreshFailureReason); await expect(tokens.refresh(refreshToken)).resolves.toBe(null); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { + expect(mockElasticsearchClient.security.getToken).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.security.getToken).toHaveBeenCalledWith({ body: { grant_type: 'refresh_token', refresh_token: refreshToken }, }); }); @@ -81,19 +102,23 @@ describe('Tokens', () => { it('returns token pair if refresh API call succeeds', async () => { const authenticationInfo = mockAuthenticatedUser(); const tokenPair = { accessToken: 'access-token', refreshToken: 'refresh-token' }; - mockClusterClient.callAsInternalUser.mockResolvedValue({ - access_token: tokenPair.accessToken, - refresh_token: tokenPair.refreshToken, - authentication: authenticationInfo, - }); + mockElasticsearchClient.security.getToken.mockResolvedValue( + securityMock.createApiResponse({ + body: { + access_token: tokenPair.accessToken, + refresh_token: tokenPair.refreshToken, + authentication: authenticationInfo, + }, + }) + ); await expect(tokens.refresh(refreshToken)).resolves.toEqual({ authenticationInfo, ...tokenPair, }); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { + expect(mockElasticsearchClient.security.getToken).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.security.getToken).toHaveBeenCalledWith({ body: { grant_type: 'refresh_token', refresh_token: refreshToken }, }); }); @@ -101,158 +126,165 @@ describe('Tokens', () => { describe('invalidate()', () => { for (const [description, failureReason] of [ - ['an unknown error', new Error('failed to delete token')], - ['a 404 error without body', { statusCode: 404 }], + [ + 'an unknown error', + new errors.ResponseError( + securityMock.createApiResponse( + securityMock.createApiResponse({ body: { message: 'failed to delete token' } }) + ) + ), + ], + [ + 'a 404 error without body', + new errors.ResponseError( + securityMock.createApiResponse( + securityMock.createApiResponse({ statusCode: 404, body: {} }) + ) + ), + ], ] as Array<[string, object]>) { it(`throws if call to delete access token responds with ${description}`, async () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - mockClusterClient.callAsInternalUser.mockImplementation((methodName, args: any) => { + mockElasticsearchClient.security.invalidateToken.mockImplementation((args: any) => { if (args && args.body && args.body.token) { - return Promise.reject(failureReason); + return Promise.reject(failureReason) as any; } - return Promise.resolve({ invalidated_tokens: 1 }); + return Promise.resolve( + securityMock.createApiResponse({ body: { invalidated_tokens: 1 } }) + ) as any; }); await expect(tokens.invalidate(tokenPair)).rejects.toBe(failureReason); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(2); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { - body: { token: tokenPair.accessToken }, - } - ); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { - body: { refresh_token: tokenPair.refreshToken }, - } - ); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledTimes(2); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { token: tokenPair.accessToken }, + }); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { refresh_token: tokenPair.refreshToken }, + }); }); it(`throws if call to delete refresh token responds with ${description}`, async () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - mockClusterClient.callAsInternalUser.mockImplementation((methodName, args: any) => { + mockElasticsearchClient.security.invalidateToken.mockImplementation((args: any) => { if (args && args.body && args.body.refresh_token) { - return Promise.reject(failureReason); + return Promise.reject(failureReason) as any; } - return Promise.resolve({ invalidated_tokens: 1 }); + return Promise.resolve( + securityMock.createApiResponse({ body: { invalidated_tokens: 1 } }) + ) as any; }); await expect(tokens.invalidate(tokenPair)).rejects.toBe(failureReason); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(2); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { - body: { token: tokenPair.accessToken }, - } - ); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { - body: { refresh_token: tokenPair.refreshToken }, - } - ); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledTimes(2); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { token: tokenPair.accessToken }, + }); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { refresh_token: tokenPair.refreshToken }, + }); }); } it('invalidates all provided tokens', async () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - mockClusterClient.callAsInternalUser.mockResolvedValue({ invalidated_tokens: 1 }); + mockElasticsearchClient.security.invalidateToken.mockResolvedValue( + securityMock.createApiResponse({ body: { invalidated_tokens: 1 } }) + ); await expect(tokens.invalidate(tokenPair)).resolves.toBe(undefined); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(2); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { body: { token: tokenPair.accessToken } } - ); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { body: { refresh_token: tokenPair.refreshToken } } - ); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledTimes(2); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { token: tokenPair.accessToken }, + }); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { refresh_token: tokenPair.refreshToken }, + }); }); it('invalidates only access token if only access token is provided', async () => { const tokenPair = { accessToken: 'foo' }; - mockClusterClient.callAsInternalUser.mockResolvedValue({ invalidated_tokens: 1 }); + mockElasticsearchClient.security.invalidateToken.mockResolvedValue( + securityMock.createApiResponse({ body: { invalidated_tokens: 1 } }) + ); await expect(tokens.invalidate(tokenPair)).resolves.toBe(undefined); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { body: { token: tokenPair.accessToken } } - ); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { token: tokenPair.accessToken }, + }); }); it('invalidates only refresh token if only refresh token is provided', async () => { const tokenPair = { refreshToken: 'foo' }; - mockClusterClient.callAsInternalUser.mockResolvedValue({ invalidated_tokens: 1 }); + mockElasticsearchClient.security.invalidateToken.mockResolvedValue( + securityMock.createApiResponse({ body: { invalidated_tokens: 1 } }) + ); await expect(tokens.invalidate(tokenPair)).resolves.toBe(undefined); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { body: { refresh_token: tokenPair.refreshToken } } - ); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { refresh_token: tokenPair.refreshToken }, + }); }); for (const [description, response] of [ - ['none of the tokens were invalidated', Promise.resolve({ invalidated_tokens: 0 })], + [ + 'none of the tokens were invalidated', + Promise.resolve(securityMock.createApiResponse({ body: { invalidated_tokens: 0 } })), + ], [ '404 error is returned', - Promise.reject({ statusCode: 404, body: { invalidated_tokens: 0 } }), + Promise.resolve( + securityMock.createApiResponse({ statusCode: 404, body: { invalidated_tokens: 0 } }) + ), ], - ] as Array<[string, Promise]>) { + ] as Array<[string, any]>) { it(`does not fail if ${description}`, async () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - mockClusterClient.callAsInternalUser.mockImplementation(() => response); + mockElasticsearchClient.security.invalidateToken.mockImplementation(() => response); await expect(tokens.invalidate(tokenPair)).resolves.toBe(undefined); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(2); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { - body: { token: tokenPair.accessToken }, - } - ); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { - body: { refresh_token: tokenPair.refreshToken }, - } - ); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledTimes(2); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { token: tokenPair.accessToken }, + }); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { refresh_token: tokenPair.refreshToken }, + }); }); } it('does not fail if more than one token per access or refresh token were invalidated', async () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - mockClusterClient.callAsInternalUser.mockResolvedValue({ invalidated_tokens: 5 }); + mockElasticsearchClient.security.invalidateToken.mockResolvedValue( + securityMock.createApiResponse({ body: { invalidated_tokens: 5 } }) + ); await expect(tokens.invalidate(tokenPair)).resolves.toBe(undefined); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(2); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { body: { token: tokenPair.accessToken } } - ); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.deleteAccessToken', - { body: { refresh_token: tokenPair.refreshToken } } - ); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledTimes(2); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { token: tokenPair.accessToken }, + }); + expect(mockElasticsearchClient.security.invalidateToken).toHaveBeenCalledWith({ + body: { refresh_token: tokenPair.refreshToken }, + }); }); }); }); diff --git a/x-pack/plugins/security/server/authentication/tokens.ts b/x-pack/plugins/security/server/authentication/tokens.ts index a435452ae112f..7bee3dfe1c5a0 100644 --- a/x-pack/plugins/security/server/authentication/tokens.ts +++ b/x-pack/plugins/security/server/authentication/tokens.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyClusterClient, Logger } from '../../../../../src/core/server'; +import type { ElasticsearchClient, Logger } from '../../../../../src/core/server'; import type { AuthenticationInfo } from '../elasticsearch'; import { getErrorStatusCode } from '../errors'; @@ -42,9 +42,7 @@ export class Tokens { */ private readonly logger: Logger; - constructor( - private readonly options: Readonly<{ client: ILegacyClusterClient; logger: Logger }> - ) { + constructor(private readonly options: Readonly<{ client: ElasticsearchClient; logger: Logger }>) { this.logger = options.logger; } @@ -59,9 +57,13 @@ export class Tokens { access_token: accessToken, refresh_token: refreshToken, authentication: authenticationInfo, - } = await this.options.client.callAsInternalUser('shield.getAccessToken', { - body: { grant_type: 'refresh_token', refresh_token: existingRefreshToken }, - }); + } = ( + await this.options.client.security.getToken<{ + access_token: string; + refresh_token: string; + authentication: AuthenticationInfo; + }>({ body: { grant_type: 'refresh_token', refresh_token: existingRefreshToken } }) + ).body; this.logger.debug('Access token has been successfully refreshed.'); @@ -108,10 +110,10 @@ export class Tokens { let invalidatedTokensCount; try { invalidatedTokensCount = ( - await this.options.client.callAsInternalUser('shield.deleteAccessToken', { + await this.options.client.security.invalidateToken<{ invalidated_tokens: number }>({ body: { refresh_token: refreshToken }, }) - ).invalidated_tokens; + ).body.invalidated_tokens; } catch (err) { this.logger.debug(`Failed to invalidate refresh token: ${err.message}`); @@ -140,10 +142,10 @@ export class Tokens { let invalidatedTokensCount; try { invalidatedTokensCount = ( - await this.options.client.callAsInternalUser('shield.deleteAccessToken', { + await this.options.client.security.invalidateToken<{ invalidated_tokens: number }>({ body: { token: accessToken }, }) - ).invalidated_tokens; + ).body.invalidated_tokens; } catch (err) { this.logger.debug(`Failed to invalidate access token: ${err.message}`); diff --git a/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts b/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts deleted file mode 100644 index 0aaad251ae642..0000000000000 --- a/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts +++ /dev/null @@ -1,214 +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 elasticsearchClientPlugin(Client: any, config: unknown, components: any) { - const ca = components.clientAction.factory; - - Client.prototype.shield = components.clientAction.namespaceFactory(); - const shield = Client.prototype.shield.prototype; - - /** - * Perform a [shield.authenticate](Retrieve details about the currently authenticated user) request - * - * @param {Object} params - An object with parameters used to carry out this action - */ - shield.authenticate = ca({ - params: {}, - url: { - fmt: '/_security/_authenticate', - }, - }); - - /** - * Asks Elasticsearch to prepare SAML authentication request to be sent to - * the 3rd-party SAML identity provider. - * - * @param {string} [acs] Optional assertion consumer service URL to use for SAML request or URL - * in the Kibana to which identity provider will post SAML response. Based on the ACS Elasticsearch - * will choose the right SAML realm. - * - * @param {string} [realm] Optional name of the Elasticsearch SAML realm to use to handle request. - * - * @returns {{realm: string, id: string, redirect: string}} Object that includes identifier - * of the SAML realm used to prepare authentication request, encrypted request token to be - * sent to Elasticsearch with SAML response and redirect URL to the identity provider that - * will be used to authenticate user. - */ - shield.samlPrepare = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/saml/prepare', - }, - }); - - /** - * Sends SAML response returned by identity provider to Elasticsearch for validation. - * - * @param {Array.} ids A list of encrypted request tokens returned within SAML - * preparation response. - * @param {string} content SAML response returned by identity provider. - * @param {string} [realm] Optional string used to identify the name of the OpenID Connect realm - * that should be used to authenticate request. - * - * @returns {{username: string, access_token: string, expires_in: number}} Object that - * includes name of the user, access token to use for any consequent requests that - * need to be authenticated and a number of seconds after which access token will expire. - */ - shield.samlAuthenticate = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/saml/authenticate', - }, - }); - - /** - * Invalidates SAML access token. - * - * @param {string} token SAML access token that needs to be invalidated. - * - * @returns {{redirect?: string}} - */ - shield.samlLogout = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/saml/logout', - }, - }); - - /** - * Invalidates SAML session based on Logout Request received from the Identity Provider. - * - * @param {string} queryString URL encoded query string provided by Identity Provider. - * @param {string} [acs] Optional assertion consumer service URL to use for SAML request or URL in the - * Kibana to which identity provider will post SAML response. Based on the ACS Elasticsearch - * will choose the right SAML realm to invalidate session. - * @param {string} [realm] Optional name of the Elasticsearch SAML realm to use to handle request. - * - * @returns {{redirect?: string}} - */ - shield.samlInvalidate = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/saml/invalidate', - }, - }); - - /** - * Asks Elasticsearch to prepare an OpenID Connect authentication request to be sent to - * the 3rd-party OpenID Connect provider. - * - * @param {string} realm The OpenID Connect realm name in Elasticsearch - * - * @returns {{state: string, nonce: string, redirect: string}} Object that includes two opaque parameters that need - * to be sent to Elasticsearch with the OpenID Connect response and redirect URL to the OpenID Connect provider that - * will be used to authenticate user. - */ - shield.oidcPrepare = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/oidc/prepare', - }, - }); - - /** - * Sends the URL to which the OpenID Connect Provider redirected the UA to Elasticsearch for validation. - * - * @param {string} state The state parameter that was returned by Elasticsearch in the - * preparation response. - * @param {string} nonce The nonce parameter that was returned by Elasticsearch in the - * preparation response. - * @param {string} redirect_uri The URL to where the UA was redirected by the OpenID Connect provider. - * @param {string} [realm] Optional string used to identify the name of the OpenID Connect realm - * that should be used to authenticate request. - * - * @returns {{username: string, access_token: string, refresh_token; string, expires_in: number}} Object that - * includes name of the user, access token to use for any consequent requests that - * need to be authenticated and a number of seconds after which access token will expire. - */ - shield.oidcAuthenticate = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/oidc/authenticate', - }, - }); - - /** - * Invalidates an access token and refresh token pair that was generated after an OpenID Connect authentication. - * - * @param {string} token An access token that was created by authenticating to an OpenID Connect realm and - * that needs to be invalidated. - * @param {string} refresh_token A refresh token that was created by authenticating to an OpenID Connect realm and - * that needs to be invalidated. - * - * @returns {{redirect?: string}} If the Elasticsearch OpenID Connect realm configuration and the - * OpenID Connect provider supports RP-initiated SLO, a URL to redirect the UA - */ - shield.oidcLogout = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/oidc/logout', - }, - }); - - /** - * Refreshes an access token. - * - * @param {string} grant_type Currently only "refresh_token" grant type is supported. - * @param {string} refresh_token One-time refresh token that will be exchanged to the new access/refresh token pair. - * - * @returns {{access_token: string, type: string, expires_in: number, refresh_token: string}} - */ - shield.getAccessToken = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/oauth2/token', - }, - }); - - /** - * Invalidates an access token. - * - * @param {string} token The access token to invalidate - * - * @returns {{created: boolean}} - */ - shield.deleteAccessToken = ca({ - method: 'DELETE', - needBody: true, - params: { - token: { - type: 'string', - }, - }, - url: { - fmt: '/_security/oauth2/token', - }, - }); - - /** - * Gets an access token in exchange to the certificate chain for the target subject distinguished name. - * - * @param {string[]} x509_certificate_chain An ordered array of base64-encoded (Section 4 of RFC4648 - not - * base64url-encoded) DER PKIX certificate values. - * - * @returns {{access_token: string, type: string, expires_in: number}} - */ - shield.delegatePKI = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/delegate_pki', - }, - }); -} diff --git a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts index 812e3e3d17f99..e58cc4b2caa52 100644 --- a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts +++ b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts @@ -5,20 +5,11 @@ */ import { BehaviorSubject } from 'rxjs'; -import { - ILegacyCustomClusterClient, - ServiceStatusLevels, - CoreStatus, -} from '../../../../../src/core/server'; +import { ServiceStatusLevels, CoreStatus } from '../../../../../src/core/server'; import { SecurityLicense, SecurityLicenseFeatures } from '../../common/licensing'; -import { elasticsearchClientPlugin } from './elasticsearch_client_plugin'; import { ElasticsearchService } from './elasticsearch_service'; -import { - coreMock, - elasticsearchServiceMock, - loggingSystemMock, -} from '../../../../../src/core/server/mocks'; +import { coreMock, loggingSystemMock } from '../../../../../src/core/server/mocks'; import { licenseMock } from '../../common/licensing/index.mock'; import { nextTick } from '@kbn/test/jest'; @@ -30,35 +21,20 @@ describe('ElasticsearchService', () => { describe('setup()', () => { it('exposes proper contract', () => { - const mockCoreSetup = coreMock.createSetup(); - const mockClusterClient = elasticsearchServiceMock.createLegacyCustomClusterClient(); - mockCoreSetup.elasticsearch.legacy.createClient.mockReturnValue(mockClusterClient); - expect( service.setup({ - elasticsearch: mockCoreSetup.elasticsearch, - status: mockCoreSetup.status, + status: coreMock.createSetup().status, license: licenseMock.create(), }) - ).toEqual({ clusterClient: mockClusterClient }); - - expect(mockCoreSetup.elasticsearch.legacy.createClient).toHaveBeenCalledTimes(1); - expect(mockCoreSetup.elasticsearch.legacy.createClient).toHaveBeenCalledWith('security', { - plugins: [elasticsearchClientPlugin], - }); + ).toBeUndefined(); }); }); describe('start()', () => { - let mockClusterClient: ILegacyCustomClusterClient; let mockLicense: jest.Mocked; let mockStatusSubject: BehaviorSubject; let mockLicenseSubject: BehaviorSubject; beforeEach(() => { - const mockCoreSetup = coreMock.createSetup(); - mockClusterClient = elasticsearchServiceMock.createLegacyCustomClusterClient(); - mockCoreSetup.elasticsearch.legacy.createClient.mockReturnValue(mockClusterClient); - mockLicenseSubject = new BehaviorSubject(({} as unknown) as SecurityLicenseFeatures); mockLicense = licenseMock.create(); mockLicense.isEnabled.mockReturnValue(false); @@ -71,20 +47,18 @@ describe('ElasticsearchService', () => { }, savedObjects: { level: ServiceStatusLevels.unavailable, summary: 'Service is NOT working' }, }); - mockCoreSetup.status.core$ = mockStatusSubject; + + const mockStatus = coreMock.createSetup().status; + mockStatus.core$ = mockStatusSubject; service.setup({ - elasticsearch: mockCoreSetup.elasticsearch, - status: mockCoreSetup.status, + status: mockStatus, license: mockLicense, }); }); it('exposes proper contract', () => { - expect(service.start()).toEqual({ - clusterClient: mockClusterClient, - watchOnlineStatus$: expect.any(Function), - }); + expect(service.start()).toEqual({ watchOnlineStatus$: expect.any(Function) }); }); it('`watchOnlineStatus$` allows tracking of Elasticsearch status', () => { @@ -199,24 +173,4 @@ describe('ElasticsearchService', () => { expect(mockHandler).toHaveBeenCalledTimes(2); }); }); - - describe('stop()', () => { - it('properly closes cluster client instance', () => { - const mockCoreSetup = coreMock.createSetup(); - const mockClusterClient = elasticsearchServiceMock.createLegacyCustomClusterClient(); - mockCoreSetup.elasticsearch.legacy.createClient.mockReturnValue(mockClusterClient); - - service.setup({ - elasticsearch: mockCoreSetup.elasticsearch, - status: mockCoreSetup.status, - license: licenseMock.create(), - }); - - expect(mockClusterClient.close).not.toHaveBeenCalled(); - - service.stop(); - - expect(mockClusterClient.close).toHaveBeenCalledTimes(1); - }); - }); }); diff --git a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.ts b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.ts index 42a83b2e5b527..ace1dc553890d 100644 --- a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.ts +++ b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.ts @@ -6,29 +6,15 @@ import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { distinctUntilChanged, filter, map, shareReplay, tap } from 'rxjs/operators'; -import { - ILegacyClusterClient, - ILegacyCustomClusterClient, - Logger, - ServiceStatusLevels, - StatusServiceSetup, - ElasticsearchServiceSetup as CoreElasticsearchServiceSetup, -} from '../../../../../src/core/server'; +import { Logger, ServiceStatusLevels, StatusServiceSetup } from '../../../../../src/core/server'; import { SecurityLicense } from '../../common/licensing'; -import { elasticsearchClientPlugin } from './elasticsearch_client_plugin'; export interface ElasticsearchServiceSetupParams { - readonly elasticsearch: CoreElasticsearchServiceSetup; readonly status: StatusServiceSetup; readonly license: SecurityLicense; } -export interface ElasticsearchServiceSetup { - readonly clusterClient: ILegacyClusterClient; -} - export interface ElasticsearchServiceStart { - readonly clusterClient: ILegacyClusterClient; readonly watchOnlineStatus$: () => Observable; } @@ -41,22 +27,13 @@ export interface OnlineStatusRetryScheduler { */ export class ElasticsearchService { readonly #logger: Logger; - #clusterClient?: ILegacyCustomClusterClient; #coreStatus$!: Observable; constructor(logger: Logger) { this.#logger = logger; } - setup({ - elasticsearch, - status, - license, - }: ElasticsearchServiceSetupParams): ElasticsearchServiceSetup { - this.#clusterClient = elasticsearch.legacy.createClient('security', { - plugins: [elasticsearchClientPlugin], - }); - + setup({ status, license }: ElasticsearchServiceSetupParams) { this.#coreStatus$ = combineLatest([status.core$, license.features$]).pipe( map( ([coreStatus]) => @@ -64,14 +41,10 @@ export class ElasticsearchService { ), shareReplay(1) ); - - return { clusterClient: this.#clusterClient }; } start(): ElasticsearchServiceStart { return { - clusterClient: this.#clusterClient!, - // We'll need to get rid of this as soon as Core's Elasticsearch service exposes this // functionality in the scope of https://github.com/elastic/kibana/issues/41983. watchOnlineStatus$: () => { @@ -120,11 +93,4 @@ export class ElasticsearchService { }, }; } - - stop() { - if (this.#clusterClient) { - this.#clusterClient.close(); - this.#clusterClient = undefined; - } - } } diff --git a/x-pack/plugins/security/server/elasticsearch/index.ts b/x-pack/plugins/security/server/elasticsearch/index.ts index 23e4876904c31..c770600db44cd 100644 --- a/x-pack/plugins/security/server/elasticsearch/index.ts +++ b/x-pack/plugins/security/server/elasticsearch/index.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AuthenticatedUser } from '../../common/model'; +import type { AuthenticatedUser } from '../../common/model'; export type AuthenticationInfo = Omit; export { ElasticsearchService, - ElasticsearchServiceSetup, ElasticsearchServiceStart, OnlineStatusRetryScheduler, } from './elasticsearch_service'; diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index df30d1bf9d6f6..7d8f3cf36a4ad 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -14,7 +14,7 @@ function createSetupMock() { const mockAuthz = authorizationMock.create(); return { audit: auditServiceMock.create(), - authc: authenticationServiceMock.createSetup(), + authc: { getCurrentUser: jest.fn() }, authz: { actions: mockAuthz.actions, checkPrivilegesWithRequest: mockAuthz.checkPrivilegesWithRequest, diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 54efdbdccbb77..256eca376fa02 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -6,11 +6,10 @@ import { of } from 'rxjs'; import { ByteSizeValue } from '@kbn/config-schema'; -import { ILegacyCustomClusterClient } from '../../../../src/core/server'; import { ConfigSchema } from './config'; import { Plugin, PluginSetupDependencies, PluginStartDependencies } from './plugin'; -import { coreMock, elasticsearchServiceMock } from '../../../../src/core/server/mocks'; +import { coreMock } from '../../../../src/core/server/mocks'; import { featuresPluginMock } from '../../features/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; import { licensingMock } from '../../licensing/server/mocks'; @@ -19,7 +18,6 @@ describe('Security Plugin', () => { let plugin: Plugin; let mockCoreSetup: ReturnType; let mockCoreStart: ReturnType; - let mockClusterClient: jest.Mocked; let mockSetupDependencies: PluginSetupDependencies; let mockStartDependencies: PluginStartDependencies; beforeEach(() => { @@ -43,9 +41,6 @@ describe('Security Plugin', () => { protocol: 'https', }); - mockClusterClient = elasticsearchServiceMock.createLegacyCustomClusterClient(); - mockCoreSetup.elasticsearch.legacy.createClient.mockReturnValue(mockClusterClient); - mockSetupDependencies = ({ licensing: { license$: of({}), featureUsage: { register: jest.fn() } }, features: featuresPluginMock.createSetup(), diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 1016221cb719d..8d8e4c096f37e 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -12,6 +12,7 @@ import { SecurityOssPluginSetup } from 'src/plugins/security_oss/server'; import { CoreSetup, CoreStart, + KibanaRequest, Logger, PluginInitializerContext, } from '../../../../src/core/server'; @@ -24,22 +25,19 @@ import { import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; -import { - AuthenticationService, - AuthenticationServiceSetup, - AuthenticationServiceStart, -} from './authentication'; +import { AuthenticationService, AuthenticationServiceStart } from './authentication'; import { AuthorizationService, AuthorizationServiceSetup } from './authorization'; import { AnonymousAccessService, AnonymousAccessServiceStart } from './anonymous_access'; import { ConfigSchema, ConfigType, createConfig } from './config'; import { defineRoutes } from './routes'; import { SecurityLicenseService, SecurityLicense } from '../common/licensing'; +import { AuthenticatedUser } from '../common/model'; import { setupSavedObjects } from './saved_objects'; import { AuditService, SecurityAuditLogger, AuditServiceSetup } from './audit'; import { SecurityFeatureUsageService, SecurityFeatureUsageServiceStart } from './feature_usage'; import { securityFeatures } from './features'; import { ElasticsearchService } from './elasticsearch'; -import { SessionManagementService } from './session_management'; +import { Session, SessionManagementService } from './session_management'; import { registerSecurityUsageCollector } from './usage_collector'; import { setupSpacesClient } from './spaces'; @@ -60,7 +58,7 @@ export interface SecurityPluginSetup { /** * @deprecated Use `authc` methods from the `SecurityServiceStart` contract instead. */ - authc: Pick; + authc: { getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null }; /** * @deprecated Use `authz` methods from the `SecurityServiceStart` contract instead. */ @@ -104,8 +102,8 @@ export interface PluginStartDependencies { */ export class Plugin { private readonly logger: Logger; - private authenticationStart?: AuthenticationServiceStart; private authorizationSetup?: AuthorizationServiceSetup; + private auditSetup?: AuditServiceSetup; private anonymousAccessStart?: AnonymousAccessServiceStart; private configSubscription?: Subscription; @@ -117,6 +115,14 @@ export class Plugin { return this.config; }; + private session?: Session; + private readonly getSession = () => { + if (!this.session) { + throw new Error('Session is not available.'); + } + return this.session; + }; + private kibanaIndexName?: string; private readonly getKibanaIndexName = () => { if (!this.kibanaIndexName) { @@ -125,6 +131,17 @@ export class Plugin { return this.kibanaIndexName; }; + private readonly authenticationService = new AuthenticationService( + this.initializerContext.logger.get('authentication') + ); + private authenticationStart?: AuthenticationServiceStart; + private readonly getAuthentication = () => { + if (!this.authenticationStart) { + throw new Error(`authenticationStart is not registered!`); + } + return this.authenticationStart; + }; + private readonly featureUsageService = new SecurityFeatureUsageService(); private featureUsageServiceStart?: SecurityFeatureUsageServiceStart; private readonly getFeatureUsageService = () => { @@ -143,9 +160,6 @@ export class Plugin { private readonly sessionManagementService = new SessionManagementService( this.initializerContext.logger.get('session') ); - private readonly authenticationService = new AuthenticationService( - this.initializerContext.logger.get('authentication') - ); private readonly anonymousAccessService = new AnonymousAccessService( this.initializerContext.logger.get('anonymous-access'), this.getConfig @@ -211,46 +225,22 @@ export class Plugin { features.registerElasticsearchFeature(securityFeature) ); - const { clusterClient } = this.elasticsearchService.setup({ - elasticsearch: core.elasticsearch, - license, - status: core.status, - }); - + this.elasticsearchService.setup({ license, status: core.status }); this.featureUsageService.setup({ featureUsage: licensing.featureUsage }); + this.sessionManagementService.setup({ config, http: core.http, taskManager }); + this.authenticationService.setup({ http: core.http, license }); registerSecurityUsageCollector({ usageCollection, config, license }); - const { session } = this.sessionManagementService.setup({ - config, - clusterClient, - http: core.http, - kibanaIndexName, - taskManager, - }); - - const audit = this.auditService.setup({ + this.auditSetup = this.auditService.setup({ license, config: config.audit, logging: core.logging, http: core.http, getSpaceId: (request) => spaces?.spacesService.getSpaceId(request), - getSID: (request) => session.getSID(request), - getCurrentUser: (request) => authenticationSetup.getCurrentUser(request), - recordAuditLoggingUsage: () => this.featureUsageServiceStart?.recordAuditLoggingUsage(), - }); - const legacyAuditLogger = new SecurityAuditLogger(audit.getLogger()); - - const authenticationSetup = this.authenticationService.setup({ - legacyAuditLogger, - audit, - getFeatureUsageService: this.getFeatureUsageService, - http: core.http, - clusterClient, - config, - license, - loggers: this.initializerContext.logger, - session, + getSID: (request) => this.getSession().getSID(request), + getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request), + recordAuditLoggingUsage: () => this.getFeatureUsageService().recordAuditLoggingUsage(), }); this.anonymousAccessService.setup(); @@ -267,18 +257,18 @@ export class Plugin { buildNumber: this.initializerContext.env.packageInfo.buildNum, getSpacesService: () => spaces?.spacesService, features, - getCurrentUser: authenticationSetup.getCurrentUser, + getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request), }); setupSpacesClient({ spaces, - audit, + audit: this.auditSetup, authz: this.authorizationSetup, }); setupSavedObjects({ - legacyAuditLogger, - audit, + legacyAuditLogger: new SecurityAuditLogger(this.auditSetup.getLogger()), + audit: this.auditSetup, authz: this.authorizationSetup, savedObjects: core.savedObjects, getSpacesService: () => spaces?.spacesService, @@ -292,26 +282,20 @@ export class Plugin { config, authz: this.authorizationSetup, license, - session, + getSession: this.getSession, getFeatures: () => startServicesPromise.then((services) => services.features.getKibanaFeatures()), getFeatureUsageService: this.getFeatureUsageService, - getAuthenticationService: () => { - if (!this.authenticationStart) { - throw new Error('Authentication service is not started!'); - } - - return this.authenticationStart; - }, + getAuthenticationService: this.getAuthentication, }); return Object.freeze({ audit: { - asScoped: audit.asScoped, - getLogger: audit.getLogger, + asScoped: this.auditSetup.asScoped, + getLogger: this.auditSetup.getLogger, }, - authc: { getCurrentUser: authenticationSetup.getCurrentUser }, + authc: { getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request) }, authz: { actions: this.authorizationSetup.actions, @@ -337,11 +321,24 @@ export class Plugin { const clusterClient = core.elasticsearch.client; const { watchOnlineStatus$ } = this.elasticsearchService.start(); + const { session } = this.sessionManagementService.start({ + elasticsearchClient: clusterClient.asInternalUser, + kibanaIndexName: this.getKibanaIndexName(), + online$: watchOnlineStatus$(), + taskManager, + }); + this.session = session; - this.sessionManagementService.start({ online$: watchOnlineStatus$(), taskManager }); + const config = this.getConfig(); this.authenticationStart = this.authenticationService.start({ - http: core.http, + audit: this.auditSetup!, clusterClient, + config, + featureUsageService: this.featureUsageServiceStart, + http: core.http, + legacyAuditLogger: new SecurityAuditLogger(this.auditSetup!.getLogger()), + loggers: this.initializerContext.logger, + session, }); this.authorizationService.start({ features, clusterClient, online$: watchOnlineStatus$() }); @@ -391,7 +388,6 @@ export class Plugin { this.securityLicenseService.stop(); this.auditService.stop(); this.authorizationService.stop(); - this.elasticsearchService.stop(); this.sessionManagementService.stop(); } } diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index 4103594faba15..f7b51eeffe6ed 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -32,7 +32,7 @@ export const routeDefinitionParamsMock = { httpResources: httpResourcesMock.createRegistrar(), getFeatures: jest.fn(), getFeatureUsageService: jest.fn(), - session: sessionMock.create(), + getSession: jest.fn().mockReturnValue(sessionMock.create()), getAuthenticationService: jest.fn().mockReturnValue(authenticationServiceMock.createStart()), } as unknown) as DeeplyMockedKeys), }; diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index 2d49329fd63d3..9a8fe2e0d6d15 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -33,7 +33,7 @@ export interface RouteDefinitionParams { logger: Logger; config: ConfigType; authz: AuthorizationServiceSetup; - session: PublicMethodsOf; + getSession: () => PublicMethodsOf; license: SecurityLicense; getFeatures: () => Promise; getFeatureUsageService: () => SecurityFeatureUsageServiceStart; diff --git a/x-pack/plugins/security/server/routes/session_management/info.test.ts b/x-pack/plugins/security/server/routes/session_management/info.test.ts index c51956f3fe530..b068e80cfa859 100644 --- a/x-pack/plugins/security/server/routes/session_management/info.test.ts +++ b/x-pack/plugins/security/server/routes/session_management/info.test.ts @@ -24,7 +24,9 @@ describe('Info session routes', () => { beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); router = routeParamsMock.router; - session = routeParamsMock.session; + + session = sessionMock.create(); + routeParamsMock.getSession.mockReturnValue(session); defineSessionInfoRoutes(routeParamsMock); }); diff --git a/x-pack/plugins/security/server/routes/session_management/info.ts b/x-pack/plugins/security/server/routes/session_management/info.ts index 381127284f780..1f73edf510976 100644 --- a/x-pack/plugins/security/server/routes/session_management/info.ts +++ b/x-pack/plugins/security/server/routes/session_management/info.ts @@ -10,12 +10,12 @@ import { RouteDefinitionParams } from '..'; /** * Defines routes required for the session info. */ -export function defineSessionInfoRoutes({ router, logger, session }: RouteDefinitionParams) { +export function defineSessionInfoRoutes({ router, logger, getSession }: RouteDefinitionParams) { router.get( { path: '/internal/security/session', validate: false }, async (_context, request, response) => { try { - const sessionValue = await session.get(request); + const sessionValue = await getSession().get(request); if (sessionValue) { return response.ok({ body: { diff --git a/x-pack/plugins/security/server/routes/users/change_password.test.ts b/x-pack/plugins/security/server/routes/users/change_password.test.ts index 24e73e456619b..2c7c3f6bafc40 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.test.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.test.ts @@ -48,7 +48,10 @@ describe('Change password', () => { beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); router = routeParamsMock.router; - session = routeParamsMock.session; + + session = sessionMock.create(); + routeParamsMock.getSession.mockReturnValue(session); + authc = authenticationServiceMock.createStart(); routeParamsMock.getAuthenticationService.mockReturnValue(authc); diff --git a/x-pack/plugins/security/server/routes/users/change_password.ts b/x-pack/plugins/security/server/routes/users/change_password.ts index 7b53afceb48fd..1c9086862c37d 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.ts @@ -16,7 +16,7 @@ import { RouteDefinitionParams } from '..'; export function defineChangeUserPasswordRoutes({ getAuthenticationService, - session, + getSession, router, }: RouteDefinitionParams) { router.post( @@ -37,7 +37,7 @@ export function defineChangeUserPasswordRoutes({ const currentUser = getAuthenticationService().getCurrentUser(request); const isUserChangingOwnPassword = currentUser && currentUser.username === username && canUserChangePassword(currentUser); - const currentSession = isUserChangingOwnPassword ? await session.get(request) : null; + const currentSession = isUserChangingOwnPassword ? await getSession().get(request) : null; // If user is changing their own password they should provide a proof of knowledge their // current password via sending it in `Authorization: Basic base64(username:current password)` diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts index 4c4f8a22eee23..a471f5f4e84cb 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts @@ -33,10 +33,12 @@ describe('Access agreement view routes', () => { const routeParamsMock = routeDefinitionParamsMock.create(); router = routeParamsMock.router; httpResources = routeParamsMock.httpResources; - session = routeParamsMock.session; config = routeParamsMock.config; license = routeParamsMock.license; + session = sessionMock.create(); + routeParamsMock.getSession.mockReturnValue(session); + license.getFeatures.mockReturnValue({ allowAccessAgreement: true, } as SecurityLicenseFeatures); diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.ts b/x-pack/plugins/security/server/routes/views/access_agreement.ts index 80a1c2a20cf59..c7f694eca68ce 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.ts @@ -12,7 +12,7 @@ import { RouteDefinitionParams } from '..'; * Defines routes required for the Access Agreement view. */ export function defineAccessAgreementRoutes({ - session, + getSession, httpResources, license, config, @@ -46,7 +46,7 @@ export function defineAccessAgreementRoutes({ // authenticated with the help of HTTP authentication), that means we should safely check if // we have it and can get a corresponding configuration. try { - const sessionValue = await session.get(request); + const sessionValue = await getSession().get(request); const accessAgreement = (sessionValue && config.authc.providers[ diff --git a/x-pack/plugins/security/server/routes/views/logged_out.test.ts b/x-pack/plugins/security/server/routes/views/logged_out.test.ts index 31096bc33d686..7cc534663e2f9 100644 --- a/x-pack/plugins/security/server/routes/views/logged_out.test.ts +++ b/x-pack/plugins/security/server/routes/views/logged_out.test.ts @@ -18,7 +18,8 @@ describe('LoggedOut view routes', () => { let routeConfig: RouteConfig; beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); - session = routeParamsMock.session; + session = sessionMock.create(); + routeParamsMock.getSession.mockReturnValue(session); defineLoggedOutRoutes(routeParamsMock); diff --git a/x-pack/plugins/security/server/routes/views/logged_out.ts b/x-pack/plugins/security/server/routes/views/logged_out.ts index b35154e6a0f2a..97357118907d3 100644 --- a/x-pack/plugins/security/server/routes/views/logged_out.ts +++ b/x-pack/plugins/security/server/routes/views/logged_out.ts @@ -17,7 +17,7 @@ import { RouteDefinitionParams } from '..'; */ export function defineLoggedOutRoutes({ logger, - session, + getSession, httpResources, basePath, }: RouteDefinitionParams) { @@ -30,7 +30,7 @@ export function defineLoggedOutRoutes({ async (context, request, response) => { // Authentication flow isn't triggered automatically for this route, so we should explicitly // check whether user has an active session already. - const isUserAlreadyLoggedIn = (await session.get(request)) !== null; + const isUserAlreadyLoggedIn = (await getSession().get(request)) !== null; if (isUserAlreadyLoggedIn) { logger.debug('User is already authenticated, redirecting...'); return response.redirected({ diff --git a/x-pack/plugins/security/server/session_management/index.ts b/x-pack/plugins/security/server/session_management/index.ts index ee7ed914947a0..1d256885f49f2 100644 --- a/x-pack/plugins/security/server/session_management/index.ts +++ b/x-pack/plugins/security/server/session_management/index.ts @@ -6,6 +6,6 @@ export { Session, SessionValue } from './session'; export { - SessionManagementServiceSetup, + SessionManagementServiceStart, SessionManagementService, } from './session_management_service'; diff --git a/x-pack/plugins/security/server/session_management/session_index.test.ts b/x-pack/plugins/security/server/session_management/session_index.test.ts index 1dd47c7ff66e8..51abcfe00253c 100644 --- a/x-pack/plugins/security/server/session_management/session_index.test.ts +++ b/x-pack/plugins/security/server/session_management/session_index.test.ts @@ -4,27 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyClusterClient } from '../../../../../src/core/server'; +import { errors } from '@elastic/elasticsearch'; +import { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import { ElasticsearchClient } from '../../../../../src/core/server'; import { ConfigSchema, createConfig } from '../config'; import { getSessionIndexTemplate, SessionIndex } from './session_index'; import { loggingSystemMock, elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; +import { securityMock } from '../mocks'; import { sessionIndexMock } from './session_index.mock'; describe('Session index', () => { - let mockClusterClient: jest.Mocked; + let mockElasticsearchClient: DeeplyMockedKeys; let sessionIndex: SessionIndex; const indexName = '.kibana_some_tenant_security_session_1'; const indexTemplateName = '.kibana_some_tenant_security_session_index_template_1'; beforeEach(() => { - mockClusterClient = elasticsearchServiceMock.createLegacyClusterClient(); + mockElasticsearchClient = elasticsearchServiceMock.createElasticsearchClient(); const sessionIndexOptions = { logger: loggingSystemMock.createLogger(), kibanaIndexName: '.kibana_some_tenant', config: createConfig(ConfigSchema.validate({}), loggingSystemMock.createLogger(), { isTLSEnabled: false, }), - clusterClient: mockClusterClient, + elasticsearchClient: mockElasticsearchClient, }; sessionIndex = new SessionIndex(sessionIndexOptions); @@ -32,22 +35,21 @@ describe('Session index', () => { describe('#initialize', () => { function assertExistenceChecksPerformed() { - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.existsTemplate', { + expect(mockElasticsearchClient.indices.existsTemplate).toHaveBeenCalledWith({ name: indexTemplateName, }); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.exists', { + expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledWith({ index: getSessionIndexTemplate(indexName).index_patterns, }); } it('debounces initialize calls', async () => { - mockClusterClient.callAsInternalUser.mockImplementation(async (method) => { - if (method === 'indices.existsTemplate' || method === 'indices.exists') { - return true; - } - - throw new Error('Unexpected call'); - }); + mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: true }) + ); + mockElasticsearchClient.indices.exists.mockResolvedValue( + securityMock.createApiResponse({ body: true }) + ); await Promise.all([ sessionIndex.initialize(), @@ -56,112 +58,102 @@ describe('Session index', () => { sessionIndex.initialize(), ]); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(2); assertExistenceChecksPerformed(); }); it('creates neither index template nor index if they exist', async () => { - mockClusterClient.callAsInternalUser.mockImplementation(async (method) => { - if (method === 'indices.existsTemplate' || method === 'indices.exists') { - return true; - } - - throw new Error('Unexpected call'); - }); + mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: true }) + ); + mockElasticsearchClient.indices.exists.mockResolvedValue( + securityMock.createApiResponse({ body: true }) + ); await sessionIndex.initialize(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(2); assertExistenceChecksPerformed(); }); it('creates both index template and index if they do not exist', async () => { - mockClusterClient.callAsInternalUser.mockImplementation(async (method) => { - if (method === 'indices.existsTemplate' || method === 'indices.exists') { - return false; - } - }); + mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); + mockElasticsearchClient.indices.exists.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); await sessionIndex.initialize(); const expectedIndexTemplate = getSessionIndexTemplate(indexName); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(4); assertExistenceChecksPerformed(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.putTemplate', { + expect(mockElasticsearchClient.indices.putTemplate).toHaveBeenCalledWith({ name: indexTemplateName, body: expectedIndexTemplate, }); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.create', { + expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith({ index: expectedIndexTemplate.index_patterns, }); }); it('creates only index template if it does not exist even if index exists', async () => { - mockClusterClient.callAsInternalUser.mockImplementation(async (method) => { - if (method === 'indices.existsTemplate') { - return false; - } - - if (method === 'indices.exists') { - return true; - } - }); + mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); + mockElasticsearchClient.indices.exists.mockResolvedValue( + securityMock.createApiResponse({ body: true }) + ); await sessionIndex.initialize(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(3); assertExistenceChecksPerformed(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.putTemplate', { + expect(mockElasticsearchClient.indices.putTemplate).toHaveBeenCalledWith({ name: indexTemplateName, body: getSessionIndexTemplate(indexName), }); }); it('creates only index if it does not exist even if index template exists', async () => { - mockClusterClient.callAsInternalUser.mockImplementation(async (method) => { - if (method === 'indices.existsTemplate') { - return true; - } - - if (method === 'indices.exists') { - return false; - } - }); + mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: true }) + ); + mockElasticsearchClient.indices.exists.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); await sessionIndex.initialize(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(3); assertExistenceChecksPerformed(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.create', { + expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith({ index: getSessionIndexTemplate(indexName).index_patterns, }); }); it('does not fail if tries to create index when it exists already', async () => { - mockClusterClient.callAsInternalUser.mockImplementation(async (method) => { - if (method === 'indices.existsTemplate') { - return true; - } - - if (method === 'indices.exists') { - return false; - } - - if (method === 'indices.create') { - // eslint-disable-next-line no-throw-literal - throw { body: { error: { type: 'resource_already_exists_exception' } } }; - } - }); + mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: true }) + ); + mockElasticsearchClient.indices.exists.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); + mockElasticsearchClient.indices.create.mockRejectedValue( + new errors.ResponseError( + securityMock.createApiResponse({ + body: { error: { type: 'resource_already_exists_exception' } }, + }) + ) + ); await sessionIndex.initialize(); }); it('works properly after failure', async () => { - const unexpectedError = new Error('Uh! Oh!'); - mockClusterClient.callAsInternalUser.mockImplementationOnce(() => - Promise.reject(unexpectedError) + const unexpectedError = new errors.ResponseError( + securityMock.createApiResponse(securityMock.createApiResponse({ body: { type: 'Uh oh.' } })) + ); + mockElasticsearchClient.indices.existsTemplate.mockRejectedValueOnce(unexpectedError); + mockElasticsearchClient.indices.existsTemplate.mockResolvedValueOnce( + securityMock.createApiResponse({ body: true }) ); - mockClusterClient.callAsInternalUser.mockImplementationOnce(() => Promise.resolve(true)); await expect(sessionIndex.initialize()).rejects.toBe(unexpectedError); await expect(sessionIndex.initialize()).resolves.toBe(undefined); @@ -171,13 +163,17 @@ describe('Session index', () => { describe('cleanUp', () => { const now = 123456; beforeEach(() => { - mockClusterClient.callAsInternalUser.mockResolvedValue({}); + mockElasticsearchClient.deleteByQuery.mockResolvedValue( + securityMock.createApiResponse({ body: {} }) + ); jest.spyOn(Date, 'now').mockImplementation(() => now); }); it('throws if call to Elasticsearch fails', async () => { - const failureReason = new Error('Uh oh.'); - mockClusterClient.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse(securityMock.createApiResponse({ body: { type: 'Uh oh.' } })) + ); + mockElasticsearchClient.deleteByQuery.mockRejectedValue(failureReason); await expect(sessionIndex.cleanUp()).rejects.toBe(failureReason); }); @@ -185,53 +181,55 @@ describe('Session index', () => { it('when neither `lifespan` nor `idleTimeout` is configured', async () => { await sessionIndex.cleanUp(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('deleteByQuery', { - index: indexName, - refresh: 'wait_for', - ignore: [409, 404], - body: { - query: { - bool: { - should: [ - // All expired sessions based on the lifespan, no matter which provider they belong to. - { range: { lifespanExpiration: { lte: now } } }, - // All sessions that belong to the providers that aren't configured. - { - bool: { - must_not: { - bool: { - should: [ - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic' } }, - ], + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledWith( + { + index: indexName, + refresh: true, + body: { + query: { + bool: { + should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. + { range: { lifespanExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, }, - }, - ], - minimum_should_match: 1, + ], + minimum_should_match: 1, + }, }, }, }, - }, - // The sessions that belong to a particular provider that are expired based on the idle timeout. - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic' } }, - ], - should: [{ range: { idleTimeoutExpiration: { lte: now } } }], - minimum_should_match: 1, + // The sessions that belong to a particular provider that are expired based on the idle timeout. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [{ range: { idleTimeoutExpiration: { lte: now } } }], + minimum_should_match: 1, + }, }, - }, - ], + ], + }, }, }, }, - }); + { ignore: [409, 404] } + ); }); it('when only `lifespan` is configured', async () => { @@ -243,68 +241,70 @@ describe('Session index', () => { loggingSystemMock.createLogger(), { isTLSEnabled: false } ), - clusterClient: mockClusterClient, + elasticsearchClient: mockElasticsearchClient, }); await sessionIndex.cleanUp(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('deleteByQuery', { - index: indexName, - refresh: 'wait_for', - ignore: [409, 404], - body: { - query: { - bool: { - should: [ - // All expired sessions based on the lifespan, no matter which provider they belong to. - { range: { lifespanExpiration: { lte: now } } }, - // All sessions that belong to the providers that aren't configured. - { - bool: { - must_not: { - bool: { - should: [ - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic' } }, - ], + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledWith( + { + index: indexName, + refresh: true, + body: { + query: { + bool: { + should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. + { range: { lifespanExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, }, - }, - ], - minimum_should_match: 1, + ], + minimum_should_match: 1, + }, }, }, }, - }, - // The sessions that belong to a particular provider but don't have a configured lifespan. - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic' } }, - ], - must_not: { exists: { field: 'lifespanExpiration' } }, + // The sessions that belong to a particular provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, }, - }, - // The sessions that belong to a particular provider that are expired based on the idle timeout. - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic' } }, - ], - should: [{ range: { idleTimeoutExpiration: { lte: now } } }], - minimum_should_match: 1, + // The sessions that belong to a particular provider that are expired based on the idle timeout. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [{ range: { idleTimeoutExpiration: { lte: now } } }], + minimum_should_match: 1, + }, }, - }, - ], + ], + }, }, }, }, - }); + { ignore: [409, 404] } + ); }); it('when only `idleTimeout` is configured', async () => { @@ -317,62 +317,64 @@ describe('Session index', () => { loggingSystemMock.createLogger(), { isTLSEnabled: false } ), - clusterClient: mockClusterClient, + elasticsearchClient: mockElasticsearchClient, }); await sessionIndex.cleanUp(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('deleteByQuery', { - index: indexName, - refresh: 'wait_for', - ignore: [409, 404], - body: { - query: { - bool: { - should: [ - // All expired sessions based on the lifespan, no matter which provider they belong to. - { range: { lifespanExpiration: { lte: now } } }, - // All sessions that belong to the providers that aren't configured. - { - bool: { - must_not: { - bool: { - should: [ - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic' } }, - ], + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledWith( + { + index: indexName, + refresh: true, + body: { + query: { + bool: { + should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. + { range: { lifespanExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, }, - }, - ], - minimum_should_match: 1, + ], + minimum_should_match: 1, + }, }, }, }, - }, - // The sessions that belong to a particular provider that are either expired based on the idle timeout - // or don't have it configured at all. - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic' } }, - ], - should: [ - { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, - { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, - ], - minimum_should_match: 1, + // The sessions that belong to a particular provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, }, - }, - ], + ], + }, }, }, }, - }); + { ignore: [409, 404] } + ); }); it('when both `lifespan` and `idleTimeout` are configured', async () => { @@ -385,72 +387,74 @@ describe('Session index', () => { loggingSystemMock.createLogger(), { isTLSEnabled: false } ), - clusterClient: mockClusterClient, + elasticsearchClient: mockElasticsearchClient, }); await sessionIndex.cleanUp(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('deleteByQuery', { - index: indexName, - refresh: 'wait_for', - ignore: [409, 404], - body: { - query: { - bool: { - should: [ - // All expired sessions based on the lifespan, no matter which provider they belong to. - { range: { lifespanExpiration: { lte: now } } }, - // All sessions that belong to the providers that aren't configured. - { - bool: { - must_not: { - bool: { - should: [ - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic' } }, - ], + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledWith( + { + index: indexName, + refresh: true, + body: { + query: { + bool: { + should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. + { range: { lifespanExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, }, - }, - ], - minimum_should_match: 1, + ], + minimum_should_match: 1, + }, }, }, }, - }, - // The sessions that belong to a particular provider but don't have a configured lifespan. - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic' } }, - ], - must_not: { exists: { field: 'lifespanExpiration' } }, + // The sessions that belong to a particular provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, }, - }, - // The sessions that belong to a particular provider that are either expired based on the idle timeout - // or don't have it configured at all. - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic' } }, - ], - should: [ - { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, - { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, - ], - minimum_should_match: 1, + // The sessions that belong to a particular provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, }, - }, - ], + ], + }, }, }, }, - }); + { ignore: [409, 404] } + ); }); it('when both `lifespan` and `idleTimeout` are configured and multiple providers are enabled', async () => { @@ -478,127 +482,132 @@ describe('Session index', () => { loggingSystemMock.createLogger(), { isTLSEnabled: false } ), - clusterClient: mockClusterClient, + elasticsearchClient: mockElasticsearchClient, }); await sessionIndex.cleanUp(); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('deleteByQuery', { - index: indexName, - refresh: 'wait_for', - ignore: [409, 404], - body: { - query: { - bool: { - should: [ - // All expired sessions based on the lifespan, no matter which provider they belong to. - { range: { lifespanExpiration: { lte: now } } }, - // All sessions that belong to the providers that aren't configured. - { - bool: { - must_not: { - bool: { - should: [ - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic1' } }, - ], + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.deleteByQuery).toHaveBeenCalledWith( + { + index: indexName, + refresh: true, + body: { + query: { + bool: { + should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. + { range: { lifespanExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + ], + }, }, - }, - { - bool: { - must: [ - { term: { 'provider.type': 'saml' } }, - { term: { 'provider.name': 'saml1' } }, - ], + { + bool: { + must: [ + { term: { 'provider.type': 'saml' } }, + { term: { 'provider.name': 'saml1' } }, + ], + }, }, - }, - ], - minimum_should_match: 1, + ], + minimum_should_match: 1, + }, }, }, }, - }, - // The sessions that belong to a Basic provider but don't have a configured lifespan. - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic1' } }, - ], - must_not: { exists: { field: 'lifespanExpiration' } }, + // The sessions that belong to a Basic provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, }, - }, - // The sessions that belong to a Basic provider that are either expired based on the idle timeout - // or don't have it configured at all. - { - bool: { - must: [ - { term: { 'provider.type': 'basic' } }, - { term: { 'provider.name': 'basic1' } }, - ], - should: [ - { range: { idleTimeoutExpiration: { lte: now - 3 * globalIdleTimeout } } }, - { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, - ], - minimum_should_match: 1, + // The sessions that belong to a Basic provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * globalIdleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, }, - }, - // The sessions that belong to a SAML provider but don't have a configured lifespan. - { - bool: { - must: [ - { term: { 'provider.type': 'saml' } }, - { term: { 'provider.name': 'saml1' } }, - ], - must_not: { exists: { field: 'lifespanExpiration' } }, + // The sessions that belong to a SAML provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'saml' } }, + { term: { 'provider.name': 'saml1' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, }, - }, - // The sessions that belong to a SAML provider that are either expired based on the idle timeout - // or don't have it configured at all. - { - bool: { - must: [ - { term: { 'provider.type': 'saml' } }, - { term: { 'provider.name': 'saml1' } }, - ], - should: [ - { range: { idleTimeoutExpiration: { lte: now - 3 * samlIdleTimeout } } }, - { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, - ], - minimum_should_match: 1, + // The sessions that belong to a SAML provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'saml' } }, + { term: { 'provider.name': 'saml1' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * samlIdleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, }, - }, - ], + ], + }, }, }, }, - }); + { ignore: [409, 404] } + ); }); }); describe('#get', () => { it('throws if call to Elasticsearch fails', async () => { - const failureReason = new Error('Uh oh.'); - mockClusterClient.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse(securityMock.createApiResponse({ body: { type: 'Uh oh.' } })) + ); + mockElasticsearchClient.get.mockRejectedValue(failureReason); await expect(sessionIndex.get('some-sid')).rejects.toBe(failureReason); }); it('returns `null` if index is not found', async () => { - mockClusterClient.callAsInternalUser.mockResolvedValue({ status: 404 }); + mockElasticsearchClient.get.mockResolvedValue( + securityMock.createApiResponse({ statusCode: 404, body: { status: 404 } }) + ); await expect(sessionIndex.get('some-sid')).resolves.toBeNull(); }); it('returns `null` if session index value document is not found', async () => { - mockClusterClient.callAsInternalUser.mockResolvedValue({ - found: false, - status: 200, - }); + mockElasticsearchClient.get.mockResolvedValue( + securityMock.createApiResponse({ body: { status: 200, found: false } }) + ); await expect(sessionIndex.get('some-sid')).resolves.toBeNull(); }); @@ -612,13 +621,17 @@ describe('Session index', () => { content: 'some-encrypted-content', }; - mockClusterClient.callAsInternalUser.mockResolvedValue({ - found: true, - status: 200, - _source: indexDocumentSource, - _primary_term: 1, - _seq_no: 456, - }); + mockElasticsearchClient.get.mockResolvedValue( + securityMock.createApiResponse({ + body: { + found: true, + status: 200, + _source: indexDocumentSource, + _primary_term: 1, + _seq_no: 456, + }, + }) + ); await expect(sessionIndex.get('some-sid')).resolves.toEqual({ ...indexDocumentSource, @@ -626,19 +639,20 @@ describe('Session index', () => { metadata: { primaryTerm: 1, sequenceNumber: 456 }, }); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('get', { - id: 'some-sid', - ignore: [404], - index: indexName, - }); + expect(mockElasticsearchClient.get).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.get).toHaveBeenCalledWith( + { id: 'some-sid', index: indexName }, + { ignore: [404] } + ); }); }); describe('#create', () => { it('throws if call to Elasticsearch fails', async () => { - const failureReason = new Error('Uh oh.'); - mockClusterClient.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse(securityMock.createApiResponse({ body: { type: 'Uh oh.' } })) + ); + mockElasticsearchClient.create.mockRejectedValue(failureReason); await expect( sessionIndex.create({ @@ -653,10 +667,9 @@ describe('Session index', () => { }); it('properly stores session value in the index', async () => { - mockClusterClient.callAsInternalUser.mockResolvedValue({ - _primary_term: 321, - _seq_no: 654, - }); + mockElasticsearchClient.create.mockResolvedValue( + securityMock.createApiResponse({ body: { _primary_term: 321, _seq_no: 654 } }) + ); const sid = 'some-long-sid'; const sessionValue = { @@ -673,8 +686,8 @@ describe('Session index', () => { metadata: { primaryTerm: 321, sequenceNumber: 654 }, }); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('create', { + expect(mockElasticsearchClient.create).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.create).toHaveBeenCalledWith({ id: sid, index: indexName, body: sessionValue, @@ -685,8 +698,10 @@ describe('Session index', () => { describe('#update', () => { it('throws if call to Elasticsearch fails', async () => { - const failureReason = new Error('Uh oh.'); - mockClusterClient.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse(securityMock.createApiResponse({ body: { type: 'Uh oh.' } })) + ); + mockElasticsearchClient.index.mockRejectedValue(failureReason); await expect(sessionIndex.update(sessionIndexMock.createValue())).rejects.toBe(failureReason); }); @@ -700,21 +715,20 @@ describe('Session index', () => { content: 'some-updated-encrypted-content', }; - mockClusterClient.callAsInternalUser.mockImplementation(async (method) => { - if (method === 'get') { - return { + mockElasticsearchClient.get.mockResolvedValue( + securityMock.createApiResponse({ + body: { found: true, status: 200, _source: latestSessionValue, _primary_term: 321, _seq_no: 654, - }; - } - - if (method === 'index') { - return { status: 409 }; - } - }); + }, + }) + ); + mockElasticsearchClient.index.mockResolvedValue( + securityMock.createApiResponse({ statusCode: 409, body: { status: 409 } }) + ); const sid = 'some-long-sid'; const metadata = { primaryTerm: 123, sequenceNumber: 456 }; @@ -732,24 +746,23 @@ describe('Session index', () => { metadata: { primaryTerm: 321, sequenceNumber: 654 }, }); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(2); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('index', { - id: sid, - index: indexName, - body: sessionValue, - ifSeqNo: 456, - ifPrimaryTerm: 123, - refresh: 'wait_for', - ignore: [409], - }); + expect(mockElasticsearchClient.index).toHaveBeenCalledWith( + { + id: sid, + index: indexName, + body: sessionValue, + if_seq_no: 456, + if_primary_term: 123, + refresh: 'wait_for', + }, + { ignore: [409] } + ); }); it('properly stores session value in the index', async () => { - mockClusterClient.callAsInternalUser.mockResolvedValue({ - _primary_term: 321, - _seq_no: 654, - status: 200, - }); + mockElasticsearchClient.index.mockResolvedValue( + securityMock.createApiResponse({ body: { _primary_term: 321, _seq_no: 654, status: 200 } }) + ); const sid = 'some-long-sid'; const metadata = { primaryTerm: 123, sequenceNumber: 456 }; @@ -767,23 +780,27 @@ describe('Session index', () => { metadata: { primaryTerm: 321, sequenceNumber: 654 }, }); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('index', { - id: sid, - index: indexName, - body: sessionValue, - ifSeqNo: 456, - ifPrimaryTerm: 123, - refresh: 'wait_for', - ignore: [409], - }); + expect(mockElasticsearchClient.index).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.index).toHaveBeenCalledWith( + { + id: sid, + index: indexName, + body: sessionValue, + if_seq_no: 456, + if_primary_term: 123, + refresh: 'wait_for', + }, + { ignore: [409] } + ); }); }); describe('#clear', () => { it('throws if call to Elasticsearch fails', async () => { - const failureReason = new Error('Uh oh.'); - mockClusterClient.callAsInternalUser.mockRejectedValue(failureReason); + const failureReason = new errors.ResponseError( + securityMock.createApiResponse(securityMock.createApiResponse({ body: { type: 'Uh oh.' } })) + ); + mockElasticsearchClient.delete.mockRejectedValue(failureReason); await expect(sessionIndex.clear('some-long-sid')).rejects.toBe(failureReason); }); @@ -791,13 +808,11 @@ describe('Session index', () => { it('properly removes session value from the index', async () => { await sessionIndex.clear('some-long-sid'); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('delete', { - id: 'some-long-sid', - index: indexName, - refresh: 'wait_for', - ignore: [404], - }); + expect(mockElasticsearchClient.delete).toHaveBeenCalledTimes(1); + expect(mockElasticsearchClient.delete).toHaveBeenCalledWith( + { id: 'some-long-sid', index: indexName, refresh: 'wait_for' }, + { ignore: [404] } + ); }); }); }); diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index 45b2f4489c195..13250531d391e 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import type { ILegacyClusterClient, Logger } from '../../../../../src/core/server'; +import type { ElasticsearchClient, Logger } from '../../../../../src/core/server'; import type { AuthenticationProvider } from '../../common/model'; import type { ConfigType } from '../config'; export interface SessionIndexOptions { - readonly clusterClient: ILegacyClusterClient; + readonly elasticsearchClient: ElasticsearchClient; readonly kibanaIndexName: string; readonly config: Pick; readonly logger: Logger; @@ -137,11 +137,10 @@ export class SessionIndex { */ async get(sid: string) { try { - const response = await this.options.clusterClient.callAsInternalUser('get', { - id: sid, - ignore: [404], - index: this.indexName, - }); + const { body: response } = await this.options.elasticsearchClient.get( + { id: sid, index: this.indexName }, + { ignore: [404] } + ); const docNotFound = response.found === false; const indexNotFound = response.status === 404; @@ -176,9 +175,8 @@ export class SessionIndex { const { sid, ...sessionValueToStore } = sessionValue; try { const { - _primary_term: primaryTerm, - _seq_no: sequenceNumber, - } = await this.options.clusterClient.callAsInternalUser('create', { + body: { _primary_term: primaryTerm, _seq_no: sequenceNumber }, + } = await this.options.elasticsearchClient.create({ id: sid, // We cannot control whether index is created automatically during this operation or not. // But we can reduce probability of getting into a weird state when session is being created @@ -203,15 +201,17 @@ export class SessionIndex { async update(sessionValue: Readonly) { const { sid, metadata, ...sessionValueToStore } = sessionValue; try { - const response = await this.options.clusterClient.callAsInternalUser('index', { - id: sid, - index: this.indexName, - body: sessionValueToStore, - ifSeqNo: metadata.sequenceNumber, - ifPrimaryTerm: metadata.primaryTerm, - refresh: 'wait_for', - ignore: [409], - }); + const { body: response } = await this.options.elasticsearchClient.index( + { + id: sid, + index: this.indexName, + body: sessionValueToStore, + if_seq_no: metadata.sequenceNumber, + if_primary_term: metadata.primaryTerm, + refresh: 'wait_for', + }, + { ignore: [409] } + ); // We don't want to override changes that were made after we fetched session value or // re-create it if has been deleted already. If we detect such a case we discard changes and @@ -242,12 +242,10 @@ export class SessionIndex { try { // We don't specify primary term and sequence number as delete should always take precedence // over any updates that could happen in the meantime. - await this.options.clusterClient.callAsInternalUser('delete', { - id: sid, - index: this.indexName, - refresh: 'wait_for', - ignore: [404], - }); + await this.options.elasticsearchClient.delete( + { id: sid, index: this.indexName, refresh: 'wait_for' }, + { ignore: [404] } + ); } catch (err) { this.options.logger.error(`Failed to clear session value: ${err.message}`); throw err; @@ -267,10 +265,11 @@ export class SessionIndex { // Check if required index template exists. let indexTemplateExists = false; try { - indexTemplateExists = await this.options.clusterClient.callAsInternalUser( - 'indices.existsTemplate', - { name: sessionIndexTemplateName } - ); + indexTemplateExists = ( + await this.options.elasticsearchClient.indices.existsTemplate({ + name: sessionIndexTemplateName, + }) + ).body; } catch (err) { this.options.logger.error( `Failed to check if session index template exists: ${err.message}` @@ -283,7 +282,7 @@ export class SessionIndex { this.options.logger.debug('Session index template already exists.'); } else { try { - await this.options.clusterClient.callAsInternalUser('indices.putTemplate', { + await this.options.elasticsearchClient.indices.putTemplate({ name: sessionIndexTemplateName, body: getSessionIndexTemplate(this.indexName), }); @@ -298,9 +297,9 @@ export class SessionIndex { // always enabled, so we create session index explicitly. let indexExists = false; try { - indexExists = await this.options.clusterClient.callAsInternalUser('indices.exists', { - index: this.indexName, - }); + indexExists = ( + await this.options.elasticsearchClient.indices.exists({ index: this.indexName }) + ).body; } catch (err) { this.options.logger.error(`Failed to check if session index exists: ${err.message}`); return reject(err); @@ -311,9 +310,7 @@ export class SessionIndex { this.options.logger.debug('Session index already exists.'); } else { try { - await this.options.clusterClient.callAsInternalUser('indices.create', { - index: this.indexName, - }); + await this.options.elasticsearchClient.indices.create({ index: this.indexName }); this.options.logger.debug('Successfully created session index.'); } catch (err) { // There can be a race condition if index is created by another Kibana instance. @@ -399,12 +396,14 @@ export class SessionIndex { } try { - const response = await this.options.clusterClient.callAsInternalUser('deleteByQuery', { - index: this.indexName, - refresh: 'wait_for', - ignore: [409, 404], - body: { query: { bool: { should: deleteQueries } } }, - }); + const { body: response } = await this.options.elasticsearchClient.deleteByQuery( + { + index: this.indexName, + refresh: true, + body: { query: { bool: { should: deleteQueries } } }, + }, + { ignore: [409, 404] } + ); if (response.deleted > 0) { this.options.logger.debug( diff --git a/x-pack/plugins/security/server/session_management/session_management_service.test.ts b/x-pack/plugins/security/server/session_management/session_management_service.test.ts index 08d4c491d1556..d3e5c876c9974 100644 --- a/x-pack/plugins/security/server/session_management/session_management_service.test.ts +++ b/x-pack/plugins/security/server/session_management/session_management_service.test.ts @@ -21,7 +21,7 @@ import { loggingSystemMock, } from '../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../task_manager/server/mocks'; -import { TaskManagerStartContract } from '../../../task_manager/server'; +import { TaskManagerStartContract, TaskRunCreatorFunction } from '../../../task_manager/server'; describe('SessionManagementService', () => { let service: SessionManagementService; @@ -30,21 +30,19 @@ describe('SessionManagementService', () => { }); describe('setup()', () => { - it('exposes proper contract', () => { + it('registers cleanup task', () => { const mockCoreSetup = coreMock.createSetup(); const mockTaskManager = taskManagerMock.createSetup(); expect( service.setup({ - clusterClient: elasticsearchServiceMock.createLegacyClusterClient(), http: mockCoreSetup.http, config: createConfig(ConfigSchema.validate({}), loggingSystemMock.createLogger(), { isTLSEnabled: false, }), - kibanaIndexName: '.kibana', taskManager: mockTaskManager, }) - ).toEqual({ session: expect.any(Session) }); + ).toBeUndefined(); expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1); expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalledWith({ @@ -54,60 +52,35 @@ describe('SessionManagementService', () => { }, }); }); - - it('registers proper session index cleanup task runner', () => { - const mockSessionIndexCleanUp = jest.spyOn(SessionIndex.prototype, 'cleanUp'); - const mockTaskManager = taskManagerMock.createSetup(); - - const mockClusterClient = elasticsearchServiceMock.createLegacyClusterClient(); - mockClusterClient.callAsInternalUser.mockResolvedValue({}); - service.setup({ - clusterClient: mockClusterClient, - http: coreMock.createSetup().http, - config: createConfig(ConfigSchema.validate({}), loggingSystemMock.createLogger(), { - isTLSEnabled: false, - }), - kibanaIndexName: '.kibana', - taskManager: mockTaskManager, - }); - - const [ - [ - { - [SESSION_INDEX_CLEANUP_TASK_NAME]: { createTaskRunner }, - }, - ], - ] = mockTaskManager.registerTaskDefinitions.mock.calls; - expect(mockSessionIndexCleanUp).not.toHaveBeenCalled(); - - const runner = createTaskRunner({} as any); - runner.run(); - expect(mockSessionIndexCleanUp).toHaveBeenCalledTimes(1); - - runner.run(); - expect(mockSessionIndexCleanUp).toHaveBeenCalledTimes(2); - }); }); describe('start()', () => { let mockSessionIndexInitialize: jest.SpyInstance; let mockTaskManager: jest.Mocked; + let sessionCleanupTaskRunCreator: TaskRunCreatorFunction; beforeEach(() => { mockSessionIndexInitialize = jest.spyOn(SessionIndex.prototype, 'initialize'); mockTaskManager = taskManagerMock.createStart(); mockTaskManager.ensureScheduled.mockResolvedValue(undefined as any); - const mockCoreSetup = coreMock.createSetup(); + const mockTaskManagerSetup = taskManagerMock.createSetup(); service.setup({ - clusterClient: elasticsearchServiceMock.createLegacyClusterClient(), - http: mockCoreSetup.http, + http: coreMock.createSetup().http, config: createConfig(ConfigSchema.validate({}), loggingSystemMock.createLogger(), { isTLSEnabled: false, }), - kibanaIndexName: '.kibana', - taskManager: taskManagerMock.createSetup(), + taskManager: mockTaskManagerSetup, }); + + const [ + [ + { + [SESSION_INDEX_CLEANUP_TASK_NAME]: { createTaskRunner }, + }, + ], + ] = mockTaskManagerSetup.registerTaskDefinitions.mock.calls; + sessionCleanupTaskRunCreator = createTaskRunner; }); afterEach(() => { @@ -117,13 +90,43 @@ describe('SessionManagementService', () => { it('exposes proper contract', () => { const mockStatusSubject = new Subject(); expect( - service.start({ online$: mockStatusSubject.asObservable(), taskManager: mockTaskManager }) - ).toBeUndefined(); + service.start({ + elasticsearchClient: elasticsearchServiceMock.createElasticsearchClient(), + kibanaIndexName: '.kibana', + online$: mockStatusSubject.asObservable(), + taskManager: mockTaskManager, + }) + ).toEqual({ session: expect.any(Session) }); + }); + + it('registers proper session index cleanup task runner', () => { + const mockSessionIndexCleanUp = jest.spyOn(SessionIndex.prototype, 'cleanUp'); + const mockStatusSubject = new Subject(); + service.start({ + elasticsearchClient: elasticsearchServiceMock.createElasticsearchClient(), + kibanaIndexName: '.kibana', + online$: mockStatusSubject.asObservable(), + taskManager: mockTaskManager, + }); + + expect(mockSessionIndexCleanUp).not.toHaveBeenCalled(); + + const runner = sessionCleanupTaskRunCreator({} as any); + runner.run(); + expect(mockSessionIndexCleanUp).toHaveBeenCalledTimes(1); + + runner.run(); + expect(mockSessionIndexCleanUp).toHaveBeenCalledTimes(2); }); it('initializes session index and schedules session index cleanup task when Elasticsearch goes online', async () => { const mockStatusSubject = new Subject(); - service.start({ online$: mockStatusSubject.asObservable(), taskManager: mockTaskManager }); + service.start({ + elasticsearchClient: elasticsearchServiceMock.createElasticsearchClient(), + kibanaIndexName: '.kibana', + online$: mockStatusSubject.asObservable(), + taskManager: mockTaskManager, + }); // ES isn't online yet. expect(mockSessionIndexInitialize).not.toHaveBeenCalled(); @@ -155,7 +158,12 @@ describe('SessionManagementService', () => { it('removes old cleanup task if cleanup interval changes', async () => { const mockStatusSubject = new Subject(); - service.start({ online$: mockStatusSubject.asObservable(), taskManager: mockTaskManager }); + service.start({ + elasticsearchClient: elasticsearchServiceMock.createElasticsearchClient(), + kibanaIndexName: '.kibana', + online$: mockStatusSubject.asObservable(), + taskManager: mockTaskManager, + }); mockTaskManager.get.mockResolvedValue({ schedule: { interval: '2000s' } } as any); @@ -185,7 +193,12 @@ describe('SessionManagementService', () => { it('does not remove old cleanup task if cleanup interval does not change', async () => { const mockStatusSubject = new Subject(); - service.start({ online$: mockStatusSubject.asObservable(), taskManager: mockTaskManager }); + service.start({ + elasticsearchClient: elasticsearchServiceMock.createElasticsearchClient(), + kibanaIndexName: '.kibana', + online$: mockStatusSubject.asObservable(), + taskManager: mockTaskManager, + }); mockTaskManager.get.mockResolvedValue({ schedule: { interval: '3600s' } } as any); @@ -206,7 +219,12 @@ describe('SessionManagementService', () => { it('schedules retry if index initialization fails', async () => { const mockStatusSubject = new Subject(); - service.start({ online$: mockStatusSubject.asObservable(), taskManager: mockTaskManager }); + service.start({ + elasticsearchClient: elasticsearchServiceMock.createElasticsearchClient(), + kibanaIndexName: '.kibana', + online$: mockStatusSubject.asObservable(), + taskManager: mockTaskManager, + }); mockSessionIndexInitialize.mockRejectedValue(new Error('ugh :/')); @@ -237,7 +255,12 @@ describe('SessionManagementService', () => { it('schedules retry if cleanup task registration fails', async () => { const mockStatusSubject = new Subject(); - service.start({ online$: mockStatusSubject.asObservable(), taskManager: mockTaskManager }); + service.start({ + elasticsearchClient: elasticsearchServiceMock.createElasticsearchClient(), + kibanaIndexName: '.kibana', + online$: mockStatusSubject.asObservable(), + taskManager: mockTaskManager, + }); mockTaskManager.ensureScheduled.mockRejectedValue(new Error('ugh :/')); @@ -277,12 +300,10 @@ describe('SessionManagementService', () => { const mockCoreSetup = coreMock.createSetup(); service.setup({ - clusterClient: elasticsearchServiceMock.createLegacyClusterClient(), http: mockCoreSetup.http, config: createConfig(ConfigSchema.validate({}), loggingSystemMock.createLogger(), { isTLSEnabled: false, }), - kibanaIndexName: '.kibana', taskManager: taskManagerMock.createSetup(), }); }); @@ -293,7 +314,12 @@ describe('SessionManagementService', () => { it('properly unsubscribes from status updates', () => { const mockStatusSubject = new Subject(); - service.start({ online$: mockStatusSubject.asObservable(), taskManager: mockTaskManager }); + service.start({ + elasticsearchClient: elasticsearchServiceMock.createElasticsearchClient(), + kibanaIndexName: '.kibana', + online$: mockStatusSubject.asObservable(), + taskManager: mockTaskManager, + }); service.stop(); diff --git a/x-pack/plugins/security/server/session_management/session_management_service.ts b/x-pack/plugins/security/server/session_management/session_management_service.ts index fc2e85d683d58..6bd9d8cb3a8fe 100644 --- a/x-pack/plugins/security/server/session_management/session_management_service.ts +++ b/x-pack/plugins/security/server/session_management/session_management_service.ts @@ -6,8 +6,8 @@ import { Observable, Subscription } from 'rxjs'; import { + ElasticsearchClient, HttpServiceSetup, - ILegacyClusterClient, Logger, SavedObjectsErrorHelpers, } from '../../../../../src/core/server'; @@ -21,17 +21,17 @@ import { Session } from './session'; export interface SessionManagementServiceSetupParams { readonly http: Pick; readonly config: ConfigType; - readonly clusterClient: ILegacyClusterClient; - readonly kibanaIndexName: string; readonly taskManager: TaskManagerSetupContract; } export interface SessionManagementServiceStartParams { + readonly elasticsearchClient: ElasticsearchClient; + readonly kibanaIndexName: string; readonly online$: Observable; readonly taskManager: TaskManagerStartContract; } -export interface SessionManagementServiceSetup { +export interface SessionManagementServiceStart { readonly session: Session; } @@ -46,34 +46,22 @@ export const SESSION_INDEX_CLEANUP_TASK_NAME = 'session_cleanup'; export class SessionManagementService { private statusSubscription?: Subscription; private sessionIndex!: SessionIndex; + private sessionCookie!: SessionCookie; private config!: ConfigType; private isCleanupTaskScheduled = false; constructor(private readonly logger: Logger) {} - setup({ - config, - clusterClient, - http, - kibanaIndexName, - taskManager, - }: SessionManagementServiceSetupParams): SessionManagementServiceSetup { + setup({ config, http, taskManager }: SessionManagementServiceSetupParams) { this.config = config; - const sessionCookie = new SessionCookie({ + this.sessionCookie = new SessionCookie({ config, createCookieSessionStorageFactory: http.createCookieSessionStorageFactory, serverBasePath: http.basePath.serverBasePath || '/', logger: this.logger.get('cookie'), }); - this.sessionIndex = new SessionIndex({ - config, - clusterClient, - kibanaIndexName, - logger: this.logger.get('index'), - }); - // Register task that will perform periodic session index cleanup. taskManager.registerTaskDefinitions({ [SESSION_INDEX_CLEANUP_TASK_NAME]: { @@ -81,18 +69,21 @@ export class SessionManagementService { createTaskRunner: () => ({ run: () => this.sessionIndex.cleanUp() }), }, }); - - return { - session: new Session({ - logger: this.logger, - sessionCookie, - sessionIndex: this.sessionIndex, - config, - }), - }; } - start({ online$, taskManager }: SessionManagementServiceStartParams) { + start({ + elasticsearchClient, + kibanaIndexName, + online$, + taskManager, + }: SessionManagementServiceStartParams): SessionManagementServiceStart { + this.sessionIndex = new SessionIndex({ + config: this.config, + elasticsearchClient, + kibanaIndexName, + logger: this.logger.get('index'), + }); + this.statusSubscription = online$.subscribe(async ({ scheduleRetry }) => { try { await Promise.all([this.sessionIndex.initialize(), this.scheduleCleanupTask(taskManager)]); @@ -100,6 +91,15 @@ export class SessionManagementService { scheduleRetry(); } }); + + return { + session: new Session({ + logger: this.logger, + sessionCookie: this.sessionCookie, + sessionIndex: this.sessionIndex, + config: this.config, + }), + }; } stop() { From cacce7a866f8eeb8b878c4427d344f34d1517460 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 21 Jan 2021 14:51:29 -0700 Subject: [PATCH 73/83] [ftr/verbose_instance] check for `.finally()` before using it (#88998) Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../lib/providers/verbose_instance.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/kbn-test/src/functional_test_runner/lib/providers/verbose_instance.ts b/packages/kbn-test/src/functional_test_runner/lib/providers/verbose_instance.ts index 248b55d85d8f5..cc2ecad82fb19 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/providers/verbose_instance.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/providers/verbose_instance.ts @@ -65,7 +65,11 @@ export function createVerboseInstance( } const { returned } = result; - if (returned && typeof returned.then === 'function') { + if ( + returned && + typeof returned.then === 'function' && + typeof returned.finally === 'function' + ) { return returned.finally(() => { log.indent(-2); }); From c495093f76e3ee899e9298970811b82e0627745f Mon Sep 17 00:00:00 2001 From: Constance Date: Thu, 21 Jan 2021 14:39:14 -0800 Subject: [PATCH 74/83] [App Search] Move generateEnginePath out from EngineLogic values to its own helper (#89022) * [Feedback] Move generateEnginePath to its own standalone helper - instead of living inside EngineLogic.values - I forgot Kea lets us do this now! * Update all components using generateEngineRouter to import helper directly --- .../app_search/__mocks__/engine_logic.mock.ts | 12 ++++---- .../analytics/analytics_router.test.tsx | 4 +-- .../components/analytics/analytics_router.tsx | 5 +--- .../document_creation_buttons.test.tsx | 5 ++-- .../document_creation_buttons.tsx | 5 ++-- .../documents/document_detail_logic.ts | 6 ++-- .../components/engine/engine_logic.test.ts | 23 --------------- .../components/engine/engine_logic.ts | 11 -------- .../components/engine/engine_nav.tsx | 3 +- .../app_search/components/engine/index.ts | 1 + .../components/engine/utils.test.ts | 28 +++++++++++++++++++ .../app_search/components/engine/utils.ts | 17 +++++++++++ .../components/recent_api_logs.test.tsx | 4 +-- .../components/recent_api_logs.tsx | 5 +--- .../components/total_charts.test.tsx | 3 +- .../components/total_charts.tsx | 3 +- 16 files changed, 65 insertions(+), 70 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts index 5c327f64d7775..6326a41c1d2ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts @@ -8,16 +8,14 @@ import { generatePath } from 'react-router-dom'; export const mockEngineValues = { engineName: 'some-engine', - // Note: using getters allows us to use `this`, which lets tests - // override engineName and still generate correct engine names - get generateEnginePath() { - return jest.fn((path, pathParams = {}) => - generatePath(path, { engineName: this.engineName, ...pathParams }) - ); - }, engine: {}, }; +export const mockGenerateEnginePath = jest.fn((path, pathParams = {}) => + generatePath(path, { engineName: mockEngineValues.engineName, ...pathParams }) +); + jest.mock('../components/engine', () => ({ EngineLogic: { values: mockEngineValues }, + generateEnginePath: mockGenerateEnginePath, })); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx index aea107a137da1..2cc6ff32d0ad9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { setMockValues } from '../../../__mocks__'; -import { mockEngineValues } from '../../__mocks__'; +import '../../__mocks__/engine_logic.mock'; import React from 'react'; import { shallow } from 'enzyme'; @@ -16,7 +15,6 @@ import { AnalyticsRouter } from './'; describe('AnalyticsRouter', () => { // Detailed route testing is better done via E2E tests it('renders', () => { - setMockValues(mockEngineValues); const wrapper = shallow(); expect(wrapper.find(Switch)).toHaveLength(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx index 60c0f2a3fd3e8..f549a1a8d9091 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { Route, Switch, Redirect } from 'react-router-dom'; -import { useValues } from 'kea'; import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; @@ -22,7 +21,7 @@ import { ENGINE_ANALYTICS_QUERY_DETAILS_PATH, ENGINE_ANALYTICS_QUERY_DETAIL_PATH, } from '../../routes'; -import { EngineLogic } from '../engine'; +import { generateEnginePath } from '../engine'; import { ANALYTICS_TITLE, @@ -46,8 +45,6 @@ interface Props { engineBreadcrumb: BreadcrumbTrail; } export const AnalyticsRouter: React.FC = ({ engineBreadcrumb }) => { - const { generateEnginePath } = useValues(EngineLogic); - const ANALYTICS_BREADCRUMB = [...engineBreadcrumb, ANALYTICS_TITLE]; return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx index d8684355c1a81..bd4d088bc1d8a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { setMockValues, setMockActions } from '../../../__mocks__/kea.mock'; -import { mockEngineValues } from '../../__mocks__'; +import { setMockActions } from '../../../__mocks__/kea.mock'; +import '../../__mocks__/engine_logic.mock'; import React from 'react'; import { shallow } from 'enzyme'; @@ -21,7 +21,6 @@ describe('DocumentCreationButtons', () => { beforeEach(() => { jest.clearAllMocks(); - setMockValues(mockEngineValues); setMockActions(actions); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx index 93c93224b5982..3a53b3c83d9eb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { useActions, useValues } from 'kea'; +import { useActions } from 'kea'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -22,7 +22,7 @@ import { import { EuiCardTo } from '../../../shared/react_router_helpers'; import { DOCS_PREFIX, ENGINE_CRAWLER_PATH } from '../../routes'; -import { EngineLogic } from '../engine'; +import { generateEnginePath } from '../engine'; import { DocumentCreationLogic } from './'; @@ -33,7 +33,6 @@ interface Props { export const DocumentCreationButtons: React.FC = ({ disabled = false }) => { const { openDocumentCreation } = useActions(DocumentCreationLogic); - const { generateEnginePath } = useValues(EngineLogic); const crawlerLink = generateEnginePath(ENGINE_CRAWLER_PATH); return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts index b8d67ac56b3a2..8141ba73d418e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts @@ -12,7 +12,7 @@ import { KibanaLogic } from '../../../shared/kibana'; import { HttpLogic } from '../../../shared/http'; import { ENGINE_DOCUMENTS_PATH } from '../../routes'; -import { EngineLogic } from '../engine'; +import { EngineLogic, generateEnginePath } from '../engine'; import { FieldDetails } from './types'; @@ -52,7 +52,7 @@ export const DocumentDetailLogic = kea({ }), listeners: ({ actions }) => ({ getDocumentDetails: async ({ documentId }) => { - const { engineName, generateEnginePath } = EngineLogic.values; + const { engineName } = EngineLogic.values; const { navigateToUrl } = KibanaLogic.values; try { @@ -70,7 +70,7 @@ export const DocumentDetailLogic = kea({ } }, deleteDocument: async ({ documentId }) => { - const { engineName, generateEnginePath } = EngineLogic.values; + const { engineName } = EngineLogic.values; const { navigateToUrl } = KibanaLogic.values; const CONFIRM_DELETE = i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts index 32c3382cf187a..48cbaeef70c1a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -36,7 +36,6 @@ describe('EngineLogic', () => { dataLoading: true, engine: {}, engineName: '', - generateEnginePath: expect.any(Function), isMetaEngine: false, isSampleEngine: false, hasSchemaConflicts: false, @@ -198,28 +197,6 @@ describe('EngineLogic', () => { }); describe('selectors', () => { - describe('generateEnginePath', () => { - it('returns helper function that generates paths with engineName prefilled', () => { - mount({ engineName: 'hello-world' }); - - const generatedPath = EngineLogic.values.generateEnginePath('/engines/:engineName/example'); - expect(generatedPath).toEqual('/engines/hello-world/example'); - }); - - it('allows overriding engineName and filling other params', () => { - mount({ engineName: 'lorem-ipsum' }); - - const generatedPath = EngineLogic.values.generateEnginePath( - '/engines/:engineName/foo/:bar', - { - engineName: 'dolor-sit', - bar: 'baz', - } - ); - expect(generatedPath).toEqual('/engines/dolor-sit/foo/baz'); - }); - }); - describe('isSampleEngine', () => { it('should be set based on engine.sample', () => { const mockSampleEngine = { ...mockEngineData, sample: true }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts index 04d06b596080a..9f3fe721b74de 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts @@ -5,7 +5,6 @@ */ import { kea, MakeLogicType } from 'kea'; -import { generatePath } from 'react-router-dom'; import { HttpLogic } from '../../../shared/http'; @@ -16,7 +15,6 @@ interface EngineValues { dataLoading: boolean; engine: Partial; engineName: string; - generateEnginePath: Function; isMetaEngine: boolean; isSampleEngine: boolean; hasSchemaConflicts: boolean; @@ -78,15 +76,6 @@ export const EngineLogic = kea>({ ], }, selectors: ({ selectors }) => ({ - generateEnginePath: [ - () => [selectors.engineName], - (engineName) => { - const generateEnginePath = (path: string, pathParams: object = {}) => { - return generatePath(path, { engineName, ...pathParams }); - }; - return generateEnginePath; - }, - ], isMetaEngine: [() => [selectors.engine], (engine) => engine?.type === 'meta'], isSampleEngine: [() => [selectors.engine], (engine) => !!engine?.sample], hasSchemaConflicts: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index fd30e04d34932..0e5a7d56e9065 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -40,7 +40,7 @@ import { RESULT_SETTINGS_TITLE } from '../result_settings'; import { SEARCH_UI_TITLE } from '../search_ui'; import { API_LOGS_TITLE } from '../api_logs'; -import { EngineLogic } from './'; +import { EngineLogic, generateEnginePath } from './'; import { EngineDetails } from './types'; import './engine_nav.scss'; @@ -64,7 +64,6 @@ export const EngineNav: React.FC = () => { const { engineName, - generateEnginePath, dataLoading, isSampleEngine, isMetaEngine, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts index 4e7d81f73fb8d..7846eb9d03b71 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts @@ -7,3 +7,4 @@ export { EngineRouter } from './engine_router'; export { EngineNav } from './engine_nav'; export { EngineLogic } from './engine_logic'; +export { generateEnginePath } from './utils'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.test.ts new file mode 100644 index 0000000000000..cff4065c13f5e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mockEngineValues } from '../../__mocks__'; + +import { generateEnginePath } from './utils'; + +describe('generateEnginePath', () => { + mockEngineValues.engineName = 'hello-world'; + + it('generates paths with engineName filled from state', () => { + expect(generateEnginePath('/engines/:engineName/example')).toEqual( + '/engines/hello-world/example' + ); + }); + + it('allows overriding engineName and filling other params', () => { + expect( + generateEnginePath('/engines/:engineName/foo/:bar', { + engineName: 'override', + bar: 'baz', + }) + ).toEqual('/engines/override/foo/baz'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.ts new file mode 100644 index 0000000000000..b7efcbb6e6b27 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.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. + */ + +import { generatePath } from 'react-router-dom'; + +import { EngineLogic } from './'; + +/** + * Generate a path with engineName automatically filled from EngineLogic state + */ +export const generateEnginePath = (path: string, pathParams: object = {}) => { + const { engineName } = EngineLogic.values; + return generatePath(path, { engineName, ...pathParams }); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx index 9da63ca639bbf..d7d22cafee432 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { setMockValues } from '../../../../__mocks__/kea.mock'; -import { mockEngineValues } from '../../../__mocks__'; +import '../../../__mocks__/engine_logic.mock'; import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; @@ -19,7 +18,6 @@ describe('RecentApiLogs', () => { beforeAll(() => { jest.clearAllMocks(); - setMockValues(mockEngineValues); wrapper = shallow(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx index 19c931cefc1e3..207666ef67466 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import { useValues } from 'kea'; import { EuiPageContent, @@ -17,14 +16,12 @@ import { import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { ENGINE_API_LOGS_PATH } from '../../../routes'; -import { EngineLogic } from '../../engine'; +import { generateEnginePath } from '../../engine'; import { RECENT_API_EVENTS } from '../../api_logs/constants'; import { VIEW_API_LOGS } from '../constants'; export const RecentApiLogs: React.FC = () => { - const { generateEnginePath } = useValues(EngineLogic); - return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx index 98718dea7130f..14fb19b8ca2be 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx @@ -5,7 +5,7 @@ */ import { setMockValues } from '../../../../__mocks__/kea.mock'; -import { mockEngineValues } from '../../../__mocks__'; +import '../../../__mocks__/engine_logic.mock'; import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; @@ -21,7 +21,6 @@ describe('TotalCharts', () => { beforeAll(() => { jest.clearAllMocks(); setMockValues({ - ...mockEngineValues, startDate: '1970-01-01', queriesPerDay: [0, 1, 2, 3, 5, 10, 50], operationsPerDay: [0, 0, 0, 0, 0, 0, 0], diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx index 02453cc8a150f..e8454cdc95ebc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx @@ -20,7 +20,7 @@ import { import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { ENGINE_ANALYTICS_PATH, ENGINE_API_LOGS_PATH } from '../../../routes'; -import { EngineLogic } from '../../engine'; +import { generateEnginePath } from '../../engine'; import { TOTAL_QUERIES, TOTAL_API_OPERATIONS } from '../../analytics/constants'; import { VIEW_ANALYTICS, VIEW_API_LOGS, LAST_7_DAYS } from '../constants'; @@ -28,7 +28,6 @@ import { AnalyticsChart, convertToChartData } from '../../analytics'; import { EngineOverviewLogic } from '../'; export const TotalCharts: React.FC = () => { - const { generateEnginePath } = useValues(EngineLogic); const { startDate, queriesPerDay, operationsPerDay } = useValues(EngineOverviewLogic); return ( From 4281a347c6b3bb1304c8cf70ba82a34c466b2ae5 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 21 Jan 2021 17:32:18 -0600 Subject: [PATCH 75/83] [Workplace Search] Add tests for remaining Sources components (#89026) * Remove history params We already replace the history.push functionality with KibanaLogic.values.navigateToUrl but the history object was still being passed around. * Add org sources container tests * Add tests for source router * Clean up leftover history imports * Add tests for SourcesRouter * Quick refactor for cleaner existence check Optional chaining FTW * Refactor to simplify setInterval logic This commit does a refactor to move the logic for polling for status to the logic file. In doing this I realized that we were intializing sources in the SourcesView, when we are actually already initializing sources in the components that use this, which are OrganizationSources and PrivateSources, the top-level containers. Because of this, I was able to remove the useEffect entireley, as the flash messages are cleared between page transitions in Kibana and the initialization of the sources ahppens in the containers. * Add tests for SourcesView * Fix type issue --- .../organization_sources.test.tsx | 63 +++++++++ .../content_sources/organization_sources.tsx | 3 +- .../views/content_sources/source_logic.ts | 4 +- .../content_sources/source_router.test.tsx | 120 ++++++++++++++++++ .../views/content_sources/source_router.tsx | 6 +- .../views/content_sources/sources_logic.ts | 28 +++- .../content_sources/sources_router.test.tsx | 60 +++++++++ .../content_sources/sources_view.test.tsx | 64 ++++++++++ .../views/content_sources/sources_view.tsx | 23 +--- 9 files changed, 337 insertions(+), 34 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.test.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.test.tsx new file mode 100644 index 0000000000000..1050150028aec --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.test.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../__mocks__'; + +import { shallow } from 'enzyme'; + +import React from 'react'; +import { Redirect } from 'react-router-dom'; + +import { contentSources } from '../../__mocks__/content_sources.mock'; + +import { Loading } from '../../../shared/loading'; +import { SourcesTable } from '../../components/shared/sources_table'; +import { ViewContentHeader } from '../../components/shared/view_content_header'; + +import { ADD_SOURCE_PATH, getSourcesPath } from '../../routes'; + +import { OrganizationSources } from './organization_sources'; + +describe('OrganizationSources', () => { + const initializeSources = jest.fn(); + const setSourceSearchability = jest.fn(); + + const mockValues = { + contentSources, + dataLoading: false, + }; + + beforeEach(() => { + setMockActions({ + initializeSources, + setSourceSearchability, + }); + setMockValues({ ...mockValues }); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(SourcesTable)).toHaveLength(1); + expect(wrapper.find(ViewContentHeader)).toHaveLength(1); + }); + + it('returns loading when loading', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('returns redirect when no sources', () => { + setMockValues({ ...mockValues, contentSources: [] }); + const wrapper = shallow(); + + expect(wrapper.find(Redirect).prop('to')).toEqual(getSourcesPath(ADD_SOURCE_PATH, true)); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.tsx index 880df3d086ccc..fdb536dd79771 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.tsx @@ -27,10 +27,11 @@ const ORG_HEADER_DESCRIPTION = 'Organization sources are available to the entire organization and can be assigned to specific user groups.'; export const OrganizationSources: React.FC = () => { - const { initializeSources, setSourceSearchability } = useActions(SourcesLogic); + const { initializeSources, setSourceSearchability, resetSourcesState } = useActions(SourcesLogic); useEffect(() => { initializeSources(); + return resetSourcesState; }, []); const { dataLoading, contentSources } = useValues(SourcesLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index fe958db9d0232..2de70009c56a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -39,7 +39,7 @@ export interface SourceActions { ): { sourceId: string; source: { name: string } }; resetSourceState(): void; removeContentSource(sourceId: string): { sourceId: string }; - initializeSource(sourceId: string, history: object): { sourceId: string; history: object }; + initializeSource(sourceId: string): { sourceId: string }; getSourceConfigData(serviceType: string): { serviceType: string }; setButtonNotLoading(): void; } @@ -88,7 +88,7 @@ export const SourceLogic = kea>({ setSearchResults: (searchResultsResponse: SearchResultsResponse) => searchResultsResponse, setContentFilterValue: (contentFilterValue: string) => contentFilterValue, setActivePage: (activePage: number) => activePage, - initializeSource: (sourceId: string, history: object) => ({ sourceId, history }), + initializeSource: (sourceId: string) => ({ sourceId }), initializeFederatedSummary: (sourceId: string) => ({ sourceId }), searchContentSourceDocuments: (sourceId: string) => ({ sourceId }), updateContentSource: (sourceId: string, source: { name: string }) => ({ sourceId, source }), diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx new file mode 100644 index 0000000000000..ac542f57b8fd4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; +import { useParams } from 'react-router-dom'; + +import { Route, Switch } from 'react-router-dom'; + +import { contentSources } from '../../__mocks__/content_sources.mock'; + +import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; + +import { NAV } from '../../constants'; + +import { Loading } from '../../../shared/loading'; + +import { DisplaySettingsRouter } from './components/display_settings'; +import { Overview } from './components/overview'; +import { Schema } from './components/schema'; +import { SchemaChangeErrors } from './components/schema/schema_change_errors'; +import { SourceContent } from './components/source_content'; +import { SourceSettings } from './components/source_settings'; + +import { SourceRouter } from './source_router'; + +describe('SourceRouter', () => { + const initializeSource = jest.fn(); + const contentSource = contentSources[1]; + const customSource = contentSources[0]; + const mockValues = { + contentSource, + dataLoading: false, + }; + + beforeEach(() => { + setMockActions({ + initializeSource, + }); + setMockValues({ ...mockValues }); + (useParams as jest.Mock).mockImplementationOnce(() => ({ + sourceId: '1', + })); + }); + + it('returns Loading when loading', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('renders source routes (standard)', () => { + const wrapper = shallow(); + + expect(wrapper.find(Overview)).toHaveLength(1); + expect(wrapper.find(SourceSettings)).toHaveLength(1); + expect(wrapper.find(SourceContent)).toHaveLength(1); + expect(wrapper.find(Switch)).toHaveLength(1); + expect(wrapper.find(Route)).toHaveLength(3); + }); + + it('renders source routes (custom)', () => { + setMockValues({ ...mockValues, contentSource: customSource }); + const wrapper = shallow(); + + expect(wrapper.find(DisplaySettingsRouter)).toHaveLength(1); + expect(wrapper.find(Schema)).toHaveLength(1); + expect(wrapper.find(SchemaChangeErrors)).toHaveLength(1); + expect(wrapper.find(Route)).toHaveLength(6); + }); + + it('handles breadcrumbs while loading (standard)', () => { + setMockValues({ + ...mockValues, + contentSource: {}, + }); + + const loadingBreadcrumbs = ['Sources', '...']; + + const wrapper = shallow(); + + const overviewBreadCrumb = wrapper.find(SetPageChrome).at(0); + const contentBreadCrumb = wrapper.find(SetPageChrome).at(1); + const settingsBreadCrumb = wrapper.find(SetPageChrome).at(2); + + expect(overviewBreadCrumb.prop('trail')).toEqual([...loadingBreadcrumbs, NAV.OVERVIEW]); + expect(contentBreadCrumb.prop('trail')).toEqual([...loadingBreadcrumbs, NAV.CONTENT]); + expect(settingsBreadCrumb.prop('trail')).toEqual([...loadingBreadcrumbs, NAV.SETTINGS]); + }); + + it('handles breadcrumbs while loading (custom)', () => { + setMockValues({ + ...mockValues, + contentSource: { serviceType: 'custom' }, + }); + + const loadingBreadcrumbs = ['Sources', '...']; + + const wrapper = shallow(); + + const schemaBreadCrumb = wrapper.find(SetPageChrome).at(2); + const schemaErrorsBreadCrumb = wrapper.find(SetPageChrome).at(3); + const displaySettingsBreadCrumb = wrapper.find(SetPageChrome).at(4); + + expect(schemaBreadCrumb.prop('trail')).toEqual([...loadingBreadcrumbs, NAV.SCHEMA]); + expect(schemaErrorsBreadCrumb.prop('trail')).toEqual([...loadingBreadcrumbs, NAV.SCHEMA]); + expect(displaySettingsBreadCrumb.prop('trail')).toEqual([ + ...loadingBreadcrumbs, + NAV.DISPLAY_SETTINGS, + ]); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx index 089ef0cd46a00..f46743778a168 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx @@ -6,10 +6,9 @@ import React, { useEffect } from 'react'; -import { History } from 'history'; import { useActions, useValues } from 'kea'; import moment from 'moment'; -import { Route, Switch, useHistory, useParams } from 'react-router-dom'; +import { Route, Switch, useParams } from 'react-router-dom'; import { EuiButton, EuiCallOut, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; @@ -46,14 +45,13 @@ import { SourceInfoCard } from './components/source_info_card'; import { SourceSettings } from './components/source_settings'; export const SourceRouter: React.FC = () => { - const history = useHistory() as History; const { sourceId } = useParams() as { sourceId: string }; const { initializeSource } = useActions(SourceLogic); const { contentSource, dataLoading } = useValues(SourceLogic); const { isOrganization } = useValues(AppLogic); useEffect(() => { - initializeSource(sourceId, history); + initializeSource(sourceId); }, []); if (dataLoading) return ; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts index ab71f76484561..0a3d047796f49 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts @@ -77,6 +77,9 @@ interface ISourcesServerResponse { serviceTypes: Connector[]; } +let pollingInterval: number; +const POLLING_INTERVAL = 10000; + export const SourcesLogic = kea>({ path: ['enterprise_search', 'workplace_search', 'sources_logic'], actions: { @@ -169,6 +172,7 @@ export const SourcesLogic = kea>( try { const response = await HttpLogic.values.http.get(route); + actions.pollForSourceStatusChanges(); actions.onInitializeSources(response); } catch (e) { flashAPIErrors(e); @@ -181,18 +185,20 @@ export const SourcesLogic = kea>( } }, // We poll the server and if the status update, we trigger a new fetch of the sources. - pollForSourceStatusChanges: async () => { + pollForSourceStatusChanges: () => { const { isOrganization } = AppLogic.values; if (!isOrganization) return; const serverStatuses = values.serverStatuses; - const sourceStatuses = await fetchSourceStatuses(isOrganization); + pollingInterval = window.setInterval(async () => { + const sourceStatuses = await fetchSourceStatuses(isOrganization); - sourceStatuses.some((source: ContentSourceStatus) => { - if (serverStatuses && serverStatuses[source.id] !== source.status.status) { - return actions.initializeSources(); - } - }); + sourceStatuses.some((source: ContentSourceStatus) => { + if (serverStatuses && serverStatuses[source.id] !== source.status.status) { + return actions.initializeSources(); + } + }); + }, POLLING_INTERVAL); }, setSourceSearchability: async ({ sourceId, searchable }) => { const { isOrganization } = AppLogic.values; @@ -235,6 +241,14 @@ export const SourcesLogic = kea>( resetFlashMessages: () => { clearFlashMessages(); }, + resetSourcesState: () => { + clearInterval(pollingInterval); + }, + }), + events: () => ({ + beforeUnmount() { + clearInterval(pollingInterval); + }, }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx new file mode 100644 index 0000000000000..7580203e759a9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { Route, Switch, Redirect } from 'react-router-dom'; + +import { ADD_SOURCE_PATH, PERSONAL_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; + +import { SourcesRouter } from './sources_router'; + +describe('SourcesRouter', () => { + const resetSourcesState = jest.fn(); + const mockValues = { + account: { canCreatePersonalSources: true }, + isOrganization: true, + hasPlatinumLicense: true, + }; + + beforeEach(() => { + setMockActions({ + resetSourcesState, + }); + setMockValues({ ...mockValues }); + }); + + it('renders sources routes', () => { + const TOTAL_ROUTES = 62; + const wrapper = shallow(); + + expect(wrapper.find(Switch)).toHaveLength(1); + expect(wrapper.find(Route)).toHaveLength(TOTAL_ROUTES); + }); + + it('redirects when nonplatinum license and accountOnly context', () => { + setMockValues({ ...mockValues, hasPlatinumLicense: false }); + const wrapper = shallow(); + + expect(wrapper.find(Redirect).first().prop('from')).toEqual(ADD_SOURCE_PATH); + expect(wrapper.find(Redirect).first().prop('to')).toEqual(SOURCES_PATH); + }); + + it('redirects when cannot create sources', () => { + setMockValues({ ...mockValues, account: { canCreatePersonalSources: false } }); + const wrapper = shallow(); + + expect(wrapper.find(Redirect).last().prop('from')).toEqual( + getSourcesPath(ADD_SOURCE_PATH, false) + ); + expect(wrapper.find(Redirect).last().prop('to')).toEqual(PERSONAL_SOURCES_PATH); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.test.tsx new file mode 100644 index 0000000000000..7deb87f4311a5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../__mocks__'; + +import { shallow } from 'enzyme'; + +import React from 'react'; + +import { EuiModal } from '@elastic/eui'; + +import { Loading } from '../../../shared/loading'; + +import { SourcesView } from './sources_view'; + +describe('SourcesView', () => { + const resetPermissionsModal = jest.fn(); + const permissionsModal = { + addedSourceName: 'mySource', + serviceType: 'jira', + additionalConfiguration: true, + }; + + const mockValues = { + permissionsModal, + dataLoading: false, + }; + + const children =

test

; + + beforeEach(() => { + setMockActions({ + resetPermissionsModal, + }); + setMockValues({ ...mockValues }); + }); + + it('renders', () => { + const wrapper = shallow({children}); + + expect(wrapper.find('PermissionsModal')).toHaveLength(1); + expect(wrapper.find('[data-test-subj="TestChildren"]')).toHaveLength(1); + }); + + it('returns loading when loading', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow({children}); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('calls function on modal close', () => { + const wrapper = shallow({children}); + const modal = wrapper.find('PermissionsModal').dive().find(EuiModal); + modal.prop('onClose')(); + + expect(resetPermissionsModal).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx index 7e3c14b203e9e..9e6c8f5b7319e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { useActions, useValues } from 'kea'; @@ -22,8 +22,6 @@ import { EuiText, } from '@elastic/eui'; -import { clearFlashMessages } from '../../../shared/flash_messages'; - import { Loading } from '../../../shared/loading'; import { SourceIcon } from '../../components/shared/source_icon'; @@ -31,29 +29,14 @@ import { EXTERNAL_IDENTITIES_DOCS_URL, DOCUMENT_PERMISSIONS_DOCS_URL } from '../ import { SourcesLogic } from './sources_logic'; -const POLLING_INTERVAL = 10000; - interface SourcesViewProps { children: React.ReactNode; } export const SourcesView: React.FC = ({ children }) => { - const { initializeSources, pollForSourceStatusChanges, resetPermissionsModal } = useActions( - SourcesLogic - ); - + const { resetPermissionsModal } = useActions(SourcesLogic); const { dataLoading, permissionsModal } = useValues(SourcesLogic); - useEffect(() => { - initializeSources(); - const pollingInterval = window.setInterval(pollForSourceStatusChanges, POLLING_INTERVAL); - - return () => { - clearFlashMessages(); - clearInterval(pollingInterval); - }; - }, []); - if (dataLoading) return ; const PermissionsModal = ({ @@ -113,7 +96,7 @@ export const SourcesView: React.FC = ({ children }) => { return ( <> - {!!permissionsModal && permissionsModal.additionalConfiguration && ( + {permissionsModal?.additionalConfiguration && ( Date: Thu, 21 Jan 2021 17:00:24 -0700 Subject: [PATCH 76/83] [Docs] Add geo threshold and containment docs (#88783) Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- docs/user/alerting/alert-types.asciidoc | 2 +- docs/user/alerting/geo-alert-types.asciidoc | 127 ++++++++++++++++++ ...es-tracking-containment-action-options.png | Bin 0 -> 19963 bytes ...-types-tracking-containment-conditions.png | Bin 0 -> 22187 bytes .../images/alert-types-tracking-select.png | Bin 0 -> 37690 bytes ...rt-types-tracking-threshold-conditions.png | Bin 0 -> 37636 bytes docs/user/alerting/index.asciidoc | 1 + 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 docs/user/alerting/geo-alert-types.asciidoc create mode 100644 docs/user/alerting/images/alert-types-tracking-containment-action-options.png create mode 100644 docs/user/alerting/images/alert-types-tracking-containment-conditions.png create mode 100644 docs/user/alerting/images/alert-types-tracking-select.png create mode 100644 docs/user/alerting/images/alert-types-tracking-threshold-conditions.png diff --git a/docs/user/alerting/alert-types.asciidoc b/docs/user/alerting/alert-types.asciidoc index 7de5ff56228cc..7c5a957d1cf79 100644 --- a/docs/user/alerting/alert-types.asciidoc +++ b/docs/user/alerting/alert-types.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[alert-types]] -== Alert types +== Standard stack alert types {kib} supplies alert types in two ways: some are built into {kib} (these are known as stack alerts), while domain-specific alert types are registered by {kib} apps such as <>, <>, and <>. diff --git a/docs/user/alerting/geo-alert-types.asciidoc b/docs/user/alerting/geo-alert-types.asciidoc new file mode 100644 index 0000000000000..c04cf4bca4320 --- /dev/null +++ b/docs/user/alerting/geo-alert-types.asciidoc @@ -0,0 +1,127 @@ +[role="xpack"] +[[geo-alert-types]] +== Geo alert types + +experimental[] Two additional stack alerts are available: +<> and <>. To enable, +add the following configuration to your `kibana.yml`: + +```yml +xpack.stack_alerts.enableGeoAlerting: true +``` + +As with other stack alerts, you need `all` access to the *Stack Alerts* feature +to be able to create and edit either of the geo alerts. +See <> for more information on configuring roles that provide access to this feature. + +[float] +=== Geo alert requirements + +To create either a *Tracking threshold* or a *Tracking containment* alert, the +following requirements must be present: + +- *Tracks index or index pattern*: An index containing a `geo_point` field, `date` field, +and some form of entity identifier. An entity identifier is a `keyword` or `number` +field that consistently identifies the entity to be tracked. The data in this index should be dynamically +updating so that there are entity movements to alert upon. +- *Boundaries index or index pattern*: An index containing `geo_shape` data, such as boundary data and bounding box data. +This data is presumed to be static (not updating). Shape data matching the query is +harvested once when the alert is created and anytime after when the alert is re-enabled +after disablement. + +By design, current interval entity locations (_current_ is determined by `date` in +the *Tracked index or index pattern*) are queried to determine if they are contained +within any monitored boundaries. Entity +data should be somewhat "real time", meaning the dates of new documents aren’t older +than the current time minus the amount of the interval. If data older than +`now - ` is ingested, it won't trigger an alert. + +[float] +=== Creating a geo alert +Both *threshold* and *containment* alerts can be created by clicking the *Create* +button in the <>. +Complete the <>. +Select <> to generate an alert when an entity crosses a boundary, and you desire the +ability to highlight lines of crossing on a custom map. +Select +<> if an entity should send out constant alerts +while contained within a boundary (this feature is optional) or if the alert is generally +just more focused around activity when an entity exists within a shape. + +[role="screenshot"] +image::images/alert-types-tracking-select.png[Choosing a tracking alert type] + +[NOTE] +================================================== +With recent advances in the alerting framework, most of the features +available in Tracking threshold alerts can be replicated with just +a little more work in Tracking containment alerts. The capabilities of Tracking +threshold alerts may be deprecated or folded into Tracking containment alerts +in the future. +================================================== + +[float] +[[alert-type-tracking-threshold]] +=== Tracking threshold +The Tracking threshold alert type runs an {es} query over indices, comparing the latest +entity locations with their previous locations. In the event that an entity has crossed a +boundary from the selected boundary index, an alert may be generated. + +[float] +==== Defining the conditions +Tracking threshold has a *Delayed evaluation offset* and 4 clauses that define the +condition to detect, as well as 2 Kuery bars used to provide additional filtering +context for each of the indices. + +[role="screenshot"] +image::images/alert-types-tracking-threshold-conditions.png[Five clauses define the condition to detect] + + +Delayed evaluation offset:: If a data source lags or is intermittent, you may supply +an optional value to evaluate alert conditions following a fixed delay. For instance, if data +is consistently indexed 5-10 minutes following its original timestamp, a *Delayed evaluation +offset* of `10 minutes` would ensure that alertable instances are still captured. +Index (entity):: This clause requires an *index or index pattern*, a *time field* that will be used for the *time window*, and a *`geo_point` field* for tracking. +By:: This clause specifies the field to use in the previously provided +*index or index pattern* for tracking Entities. An entity is a `keyword` +or `number` field that consistently identifies the entity to be tracked. +When entity:: This clause specifies which crossing option to track. The values +*Entered*, *Exited*, and *Crossed* can be selected to indicate which crossing conditions +should trigger an alert. *Entered* alerts on entry into a boundary, *Exited* alerts on exit +from a boundary, and *Crossed* alerts on all boundary crossings whether they be entrances +or exits. +Index (Boundary):: This clause requires an *index or index pattern*, a *`geo_shape` field* +identifying boundaries, and an optional *Human-readable boundary name* for better alerting +messages. + +[float] +[[alert-type-tracking-containment]] +=== Tracking containment +The Tracking containment alert type runs an {es} query over indices, determining if any +documents are currently contained within any boundaries from the specified boundary index. +In the event that an entity is contained within a boundary, an alert may be generated. + +[float] +==== Defining the conditions +Tracking containment alerts have 3 clauses that define the condition to detect, +as well as 2 Kuery bars used to provide additional filtering context for each of the indices. + +[role="screenshot"] +image::images/alert-types-tracking-containment-conditions.png[Five clauses define the condition to detect] + +Index (entity):: This clause requires an *index or index pattern*, a *time field* that will be used for the *time window*, and a *`geo_point` field* for tracking. +When entity:: This clause specifies which crossing option to track. The values +*Entered*, *Exited*, and *Crossed* can be selected to indicate which crossing conditions +should trigger an alert. *Entered* alerts on entry into a boundary, *Exited* alerts on exit +from a boundary, and *Crossed* alerts on all boundary crossings whether they be entrances +or exits. +Index (Boundary):: This clause requires an *index or index pattern*, a *`geo_shape` field* +identifying boundaries, and an optional *Human-readable boundary name* for better alerting +messages. + +Conditions for how an alert is tracked can be specified uniquely for each individual action. +An alert can be triggered either when a containment condition is met or when an entity +is no longer contained. + +[role="screenshot"] +image::images/alert-types-tracking-containment-action-options.png[Five clauses define the condition to detect] diff --git a/docs/user/alerting/images/alert-types-tracking-containment-action-options.png b/docs/user/alerting/images/alert-types-tracking-containment-action-options.png new file mode 100644 index 0000000000000000000000000000000000000000..c0a045f8273829d7a9fbb0d07b19c2a137dbc46f GIT binary patch literal 19963 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 JPDHLkV1n`+-|qkb literal 0 HcmV?d00001 diff --git a/docs/user/alerting/images/alert-types-tracking-containment-conditions.png b/docs/user/alerting/images/alert-types-tracking-containment-conditions.png new file mode 100644 index 0000000000000000000000000000000000000000..32c17d2245d23c9d526e252b802deb75c6984931 GIT binary patch literal 22187 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

Z=b$?CE%Gp~U{yd9HNKQ^@G22Il7oCaCoLa&z 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 Date: Thu, 21 Jan 2021 18:27:27 -0700 Subject: [PATCH 77/83] [Maps] fix setting "apply global time" switch not working with blended vector layer (#88996) * [Maps] fix setting "apply global time" switch not working with blended vector layer * review feedback Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../blended_vector_layer.test.tsx | 46 +++++++++++-------- .../blended_vector_layer.ts | 1 + .../maps/public/classes/layers/layer.tsx | 5 +- .../connected_components/mb_map/mb_map.tsx | 2 +- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.test.tsx index 1321593f015c0..e029480bd8616 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.test.tsx +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.test.tsx @@ -7,7 +7,10 @@ import { SCALING_TYPES, SOURCE_TYPES } from '../../../../common/constants'; import { BlendedVectorLayer } from './blended_vector_layer'; import { ESSearchSource } from '../../sources/es_search_source'; -import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types'; +import { + AbstractESSourceDescriptor, + ESGeoGridSourceDescriptor, +} from '../../../../common/descriptor_types'; jest.mock('../../../kibana_services', () => { return { @@ -53,27 +56,12 @@ describe('getSource', () => { expect(source.cloneDescriptor().type).toBe(SOURCE_TYPES.ES_GEO_GRID); }); - test('cluster source applyGlobalQuery should be true when document source applyGlobalQuery is true', async () => { - const blendedVectorLayer = new BlendedVectorLayer({ - source: new ESSearchSource(documentSourceDescriptor), - layerDescriptor: BlendedVectorLayer.createDescriptor( - { - sourceDescriptor: documentSourceDescriptor, - __dataRequests: [clusteredDataRequest], - }, - mapColors - ), - }); - - const source = blendedVectorLayer.getSource(); - expect((source.cloneDescriptor() as ESGeoGridSourceDescriptor).applyGlobalQuery).toBe(true); - }); - - test('cluster source applyGlobalQuery should be false when document source applyGlobalQuery is false', async () => { + test('cluster source AbstractESSourceDescriptor properties should mirror document source AbstractESSourceDescriptor properties', async () => { const blendedVectorLayer = new BlendedVectorLayer({ source: new ESSearchSource({ ...documentSourceDescriptor, applyGlobalQuery: false, + applyGlobalTime: false, }), layerDescriptor: BlendedVectorLayer.createDescriptor( { @@ -85,7 +73,27 @@ describe('getSource', () => { }); const source = blendedVectorLayer.getSource(); - expect((source.cloneDescriptor() as ESGeoGridSourceDescriptor).applyGlobalQuery).toBe(false); + const sourceDescriptor = source.cloneDescriptor() as ESGeoGridSourceDescriptor; + const abstractEsSourceDescriptor: AbstractESSourceDescriptor = { + // Purposely grabbing properties instead of using spread operator + // to ensure type check will fail when new properties are added to AbstractESSourceDescriptor. + // In the event of type check failure, ensure test is updated with new property and that new property + // is correctly passed to clustered source descriptor. + type: sourceDescriptor.type, + id: sourceDescriptor.id, + indexPatternId: sourceDescriptor.indexPatternId, + geoField: sourceDescriptor.geoField, + applyGlobalQuery: sourceDescriptor.applyGlobalQuery, + applyGlobalTime: sourceDescriptor.applyGlobalTime, + }; + expect(abstractEsSourceDescriptor).toEqual({ + type: sourceDescriptor.type, + id: sourceDescriptor.id, + geoField: 'myGeoField', + indexPatternId: 'myIndexPattern', + applyGlobalQuery: false, + applyGlobalTime: false, + } as AbstractESSourceDescriptor); }); }); diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts index 825f6ed74777a..5b33738a91a28 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts @@ -62,6 +62,7 @@ function getClusterSource(documentSource: IESSource, documentStyle: IVectorStyle requestType: RENDER_AS.POINT, }); clusterSourceDescriptor.applyGlobalQuery = documentSource.getApplyGlobalQuery(); + clusterSourceDescriptor.applyGlobalTime = documentSource.getApplyGlobalTime(); clusterSourceDescriptor.metrics = [ { type: AGG_TYPE.COUNT, diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index 060ff4d46fa2a..fe13e4f0ac2f6 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -5,6 +5,7 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ +import { Map as MbMap } from 'mapbox-gl'; import { Query } from 'src/plugins/data/public'; import _ from 'lodash'; import React, { ReactElement } from 'react'; @@ -68,7 +69,7 @@ export interface ILayer { ownsMbLayerId(mbLayerId: string): boolean; ownsMbSourceId(mbSourceId: string): boolean; canShowTooltip(): boolean; - syncLayerWithMB(mbMap: unknown): void; + syncLayerWithMB(mbMap: MbMap): void; getLayerTypeIconName(): string; isDataLoaded(): boolean; getIndexPatternIds(): string[]; @@ -418,7 +419,7 @@ export class AbstractLayer implements ILayer { return false; } - syncLayerWithMB(mbMap: unknown) { + syncLayerWithMB(mbMap: MbMap) { throw new Error('Should implement AbstractLayer#syncLayerWithMB'); } diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index 4dc765f1704a0..820453f166a46 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -332,7 +332,7 @@ export class MBMap extends Component { this.props.layerList, this.props.spatialFiltersLayer ); - this.props.layerList.forEach((layer) => layer.syncLayerWithMB(this.state.mbMap)); + this.props.layerList.forEach((layer) => layer.syncLayerWithMB(this.state.mbMap!)); syncLayerOrder(this.state.mbMap, this.props.spatialFiltersLayer, this.props.layerList); }; From a0bfdf87fd51cad4973a6d07aca66a90c98b2ed3 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 21 Jan 2021 19:35:40 -0700 Subject: [PATCH 78/83] [Maps] fix Filter shape stops showing feedback when data refreshes (#89009) * [Maps] fix Filter shape stops showing feedback when data refreshes * update comment * add curly braces around if --- .../mb_map/sort_layers.test.ts | 2 ++ .../connected_components/mb_map/sort_layers.ts | 16 ++++++++++++++++ .../public/connected_components/mb_map/utils.js | 3 ++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts index 9e85c7b04b266..4e9cb499cf704 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts @@ -135,6 +135,7 @@ describe('sortLayer', () => { { id: `${BRAVO_LAYER_ID}_circle`, type: 'circle' } as MbLayer, { id: `${SPATIAL_FILTERS_LAYER_ID}_fill`, type: 'fill' } as MbLayer, { id: `${SPATIAL_FILTERS_LAYER_ID}_circle`, type: 'circle' } as MbLayer, + { id: `gl-draw-polygon-fill-active.cold`, type: 'fill' } as MbLayer, { id: `${CHARLIE_LAYER_ID}_text`, type: 'symbol', @@ -158,6 +159,7 @@ describe('sortLayer', () => { 'alpha_text', 'alpha_circle', 'charlie_text', + 'gl-draw-polygon-fill-active.cold', 'SPATIAL_FILTERS_LAYER_ID_fill', 'SPATIAL_FILTERS_LAYER_ID_circle', ]); diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts index dda43269e32d8..adf68ffb310bc 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts @@ -28,6 +28,10 @@ export function getIsTextLayer(mbLayer: MbLayer) { }); } +export function isGlDrawLayer(mbLayerId: string) { + return mbLayerId.startsWith('gl-draw'); +} + function doesMbLayerBelongToMapLayerAndClass( mapLayer: ILayer, mbLayer: MbLayer, @@ -118,6 +122,18 @@ export function syncLayerOrder(mbMap: MbMap, spatialFiltersLayer: ILayer, layerL } let beneathMbLayerId = getBottomMbLayerId(mbLayers, spatialFiltersLayer, LAYER_CLASS.ANY); + // Ensure gl-draw layers are on top of all layerList layers + const glDrawLayer = ({ + ownsMbLayerId: (mbLayerId: string) => { + return isGlDrawLayer(mbLayerId); + }, + } as unknown) as ILayer; + moveMapLayer(mbMap, mbLayers, glDrawLayer, LAYER_CLASS.ANY, beneathMbLayerId); + const glDrawBottomMbLayerId = getBottomMbLayerId(mbLayers, glDrawLayer, LAYER_CLASS.ANY); + if (glDrawBottomMbLayerId) { + beneathMbLayerId = glDrawBottomMbLayerId; + } + // Sort map layer labels [...layerList] .reverse() diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/utils.js b/x-pack/plugins/maps/public/connected_components/mb_map/utils.js index f12f34061756f..2f8852174c29e 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/utils.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/utils.js @@ -5,6 +5,7 @@ */ import { RGBAImage } from './image_utils'; +import { isGlDrawLayer } from './sort_layers'; export function removeOrphanedSourcesAndLayers(mbMap, layerList, spatialFilterLayer) { const mbStyle = mbMap.getStyle(); @@ -17,7 +18,7 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList, spatialFilterLa } // ignore gl-draw layers - if (mbLayer.id.startsWith('gl-draw')) { + if (isGlDrawLayer(mbLayer.id)) { return; } From 58c54b6f8583eea111f5a039c422bcb17de681f6 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Fri, 22 Jan 2021 09:02:42 -0500 Subject: [PATCH 79/83] Add a high level overview of the Kibana platform and plugin development. (#87560) * add platform intro * address code review comments (wip) * incorporate more information about plugins * put back Josh's suggestion * Update dev_docs/kibana_platform_plugin_intro.mdx Co-authored-by: Brandon Kobel * try another angle * further refinements * sp * Update kibana_platform_plugin_intro.mdx Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Brandon Kobel --- .../kibana_platform_plugin_end_user.png | Bin 0 -> 355010 bytes dev_docs/assets/platform_plugin_cycle.png | Bin 0 -> 202395 bytes dev_docs/kibana_platform_plugin_intro.mdx | 305 ++++++++++++++++++ 3 files changed, 305 insertions(+) create mode 100644 dev_docs/assets/kibana_platform_plugin_end_user.png create mode 100644 dev_docs/assets/platform_plugin_cycle.png create mode 100644 dev_docs/kibana_platform_plugin_intro.mdx diff --git a/dev_docs/assets/kibana_platform_plugin_end_user.png b/dev_docs/assets/kibana_platform_plugin_end_user.png new file mode 100644 index 0000000000000000000000000000000000000000..a0e32a35ffe6054cc1c17341eb8052d3c901b92b GIT binary patch literal 355010 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 literal 0 HcmV?d00001 diff --git a/dev_docs/assets/platform_plugin_cycle.png b/dev_docs/assets/platform_plugin_cycle.png new file mode 100644 index 0000000000000000000000000000000000000000..533a38c4ed9c4db71b94c00a86c2aad026fd9aa6 GIT binary patch literal 202395 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%0D1lN=X?k?Bw1Z9_x30+ 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%0A$c=m8eAa1%T_TEM{JGJxKo06rZ4rdGQer@$1 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±a2W&NG>}5gMVrm`i{MFil6ENK1y~tibDBSWy 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 provides basic utilities to search, query, and filter data in Elasticsearch. +This code is not part of Core, but is still fundamental for building a plugin, + and we strongly encourage using this service over querying Elasticsearch directly. + + +We currently have three kinds of public services: + + - platform services provided by `core` + - platform services provided by plugins, that can, and should, be used by every plugin (e.g. ) . + - shared services provided by plugins, that are only relevant for only a few, specific plugins (e.g. "presentation utils"). + +Two common questions we encounter are: + +1. Which services are platform services? +2. What is the difference between platform code supplied by core, and platform code supplied by plugins? + +We don't have great answers to those questions today. Currently, the best answers we have are: + +1. Platform plugins are _usually_ plugins that are managed by the Platform Group, but we are starting to see some exceptions. +2. `core` code contains the most fundamental and stable services needed for plugin development. Everything else goes in a plugin. + +We will continue to focus on adding clarity around these types of services and what developers can expect from each. + + + + + +When the Kibana platform and plugin infrastructure was built, we thought of two types of code: core services, and other plugin services. We planned to keep the most stable and fundamental +code needed to build plugins inside core. + +In reality, we ended up with many platform-like services living outside of core, with no (short term) intention of moving them. We highly encourage plugin developers to use +them, so we consider them part of platform services. + +When we built our platform system, we also thought we'd end up with only a handful of large plugins outside core. Users could turn certain plugins off, to minimize the code + footprint and speed up Kibana. + +In reality, our plugin model ended up being used like micro-services. Plugins are the only form of encapsulation we provide developers, and they liked it! However, we ended + up with a ton of small plugins, that developers never intended to be uninstallable, nor tested in this manner. We are considering ways to provide developers the ability to build services + with the encapsulation + they desire, without the need to build a plugin. + +Another side effect of having many small plugins is that common code often ends up extracted into another plugin. Use case specific utilities are exported, + that are not meant to be used in a general manner. This makes our definition of "platform code" a bit trickier to define. We'd like to say "The platform is made up of + every publically exposed service", but in today's world, that wouldn't be a very accurate picture. + +We recognize the need to better clarify the relationship between core functionality, platform-like plugin functionality, and functionality exposed by other plugins. + It's something we will be working on! + + + +The main difference between core functionality and functionality supplied by plugins, is in how it is accessed. Core is +passed to plugins as the first parameter to their `start` and `setup` lifecycle functions, while plugin supplied functionality is passed as the +second parameter. Plugin dependencies must be declared explicitly inside the `kibana.json` file. Core functionality is always provided. Read the +section on [how plugins interact with eachother and core](#how-plugins-interact-with-each-other-and-core) for more information. + +## The anatomy of a plugin + +Plugins are defined as classes and present themselves to Kibana through a simple wrapper function. A plugin can have browser-side code, server-side code, +or both. There is no architectural difference between a plugin in the browser and a plugin on the server. In both places, you describe your plugin similarly, +and you interact with Core and other plugins in the same way. + +The basic file structure of a Kibana plugin named demo that has both client-side and server-side code would be: + +``` +plugins/ + demo + kibana.json [1] + public + index.ts [2] + plugin.ts [3] + server + index.ts [4] + plugin.ts [5] +``` + +### [1] kibana.json + +`kibana.json` is a static manifest file that is used to identify the plugin and to specify if this plugin has server-side code, browser-side code, or both: + +``` +{ + "id": "demo", + "version": "kibana", + "server": true, + "ui": true +} +``` + +### [2] public/index.ts + +`public/index.ts` is the entry point into the client-side code of this plugin. It must export a function named plugin, which will receive a standard set of + core capabilities as an argument. It should return an instance of its plugin class for Kibana to load. + +``` +import type { PluginInitializerContext } from 'kibana/server'; +import { DemoPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new DemoPlugin(initializerContext); +} +``` + +### [3] public/plugin.ts + +`public/plugin.ts` is the client-side plugin definition itself. Technically speaking, it does not need to be a class or even a separate file from the entry + point, but all plugins at Elastic should be consistent in this way. + + + ```ts +import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server'; + +export class DemoPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup) { + // called when plugin is setting up during Kibana's startup sequence + } + + public start(core: CoreStart) { + // called after all plugins are set up + } + + public stop() { + // called when plugin is torn down during Kibana's shutdown sequence + } +} + ``` + + +### [4] server/index.ts + +`server/index.ts` is the entry-point into the server-side code of this plugin. It is identical in almost every way to the client-side entry-point: + +### [5] server/plugin.ts + +`server/plugin.ts` is the server-side plugin definition. The shape of this plugin is the same as it’s client-side counter-part: + +```ts +import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server'; + +export class DemoPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup) { + // called when plugin is setting up during Kibana's startup sequence + } + + public start(core: CoreStart) { + // called after all plugins are set up + } + + public stop() { + // called when plugin is torn down during Kibana's shutdown sequence + } +} +``` + +Kibana does not impose any technical restrictions on how the the internals of a plugin are architected, though there are certain +considerations related to how plugins integrate with core APIs and APIs exposed by other plugins that may greatly impact how they are built. + +## Plugin lifecycles & Core services + +The various independent domains that make up core are represented by a series of services. Those services expose public interfaces that are provided to all plugins. +Services expose different features at different parts of their lifecycle. We describe the lifecycle of core services and plugins with specifically-named functions on the service definition. + +Kibana has three lifecycles: setup, start, and stop. Each plugin’s setup function is called sequentially while Kibana is setting up on the server or when it is being loaded in the browser. The start functions are called sequentially after setup has been completed for all plugins. The stop functions are called sequentially while Kibana is gracefully shutting down the server or when the browser tab or window is being closed. + +The table below explains how each lifecycle relates to the state of Kibana. + +| lifecycle | purpose | server | browser | +| ---------- | ------ | ------- | ----- | +| setup | perform "registration" work to setup environment for runtime |configure REST API endpoint, register saved object types, etc. | configure application routes in SPA, register custom UI elements in extension points, etc. | +| start | bootstrap runtime logic | respond to an incoming request, request Elasticsearch server, etc. | start polling Kibana server, update DOM tree in response to user interactions, etc.| +| stop | cleanup runtime | dispose of active handles before the server shutdown. | store session data in the LocalStorage when the user navigates away from Kibana, etc. | + +Different service interfaces can and will be passed to setup, start, and stop because certain functionality makes sense in the context of a running plugin while other types +of functionality may have restrictions or may only make sense in the context of a plugin that is stopping. + +## How plugin's interact with each other, and Core + +The lifecycle-specific contracts exposed by core services are always passed as the first argument to the equivalent lifecycle function in a plugin. +For example, the core http service exposes a function createRouter to all plugin setup functions. To use this function to register an HTTP route handler, +a plugin just accesses it off of the first argument: + +```ts +import type { CoreSetup } from 'kibana/server'; + +export class DemoPlugin { + public setup(core: CoreSetup) { + const router = core.http.createRouter(); + // handler is called when '/path' resource is requested with `GET` method + router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' })); + } +} +``` + +Unlike core, capabilities exposed by plugins are not automatically injected into all plugins. +Instead, if a plugin wishes to use the public interface provided by another plugin, it must first declare that plugin as a + dependency in it’s kibana.json manifest file. + +** foobar plugin.ts: ** + +``` +import type { Plugin } from 'kibana/server'; +export interface FoobarPluginSetup { [1] + getFoo(): string; +} + +export interface FoobarPluginStart { [1] + getBar(): string; +} + +export class MyPlugin implements Plugin { + public setup(): FoobarPluginSetup { + return { + getFoo() { + return 'foo'; + }, + }; + } + + public start(): FoobarPluginStart { + return { + getBar() { + return 'bar'; + }, + }; + } +} +``` +[1] We highly encourage plugin authors to explicitly declare public interfaces for their plugins. + + +** demo kibana.json** + +``` +{ + "id": "demo", + "requiredPlugins": ["foobar"], + "server": true, + "ui": true +} +``` + +With that specified in the plugin manifest, the appropriate interfaces are then available via the second argument of setup and/or start: + +```ts +import type { CoreSetup, CoreStart } from 'kibana/server'; +import type { FoobarPluginSetup, FoobarPluginStart } from '../../foobar/server'; + +interface DemoSetupPlugins { [1] + foobar: FoobarPluginSetup; +} + +interface DemoStartPlugins { + foobar: FoobarPluginStart; +} + +export class DemoPlugin { + public setup(core: CoreSetup, plugins: DemoSetupPlugins) { [2] + const { foobar } = plugins; + foobar.getFoo(); // 'foo' + foobar.getBar(); // throws because getBar does not exist + } + + public start(core: CoreStart, plugins: DemoStartPlugins) { [3] + const { foobar } = plugins; + foobar.getFoo(); // throws because getFoo does not exist + foobar.getBar(); // 'bar' + } + + public stop() {} +} +``` + +[1] The interface for plugin’s dependencies must be manually composed. You can do this by importing the appropriate type from the plugin and constructing an interface where the property name is the plugin’s ID. + +[2] These manually constructed types should then be used to specify the type of the second argument to the plugin. + +[3] Notice that the type for the setup and start lifecycles are different. Plugin lifecycle functions can only access the APIs that are exposed during that lifecycle. From 49d95f6fb1678af9db7243bd5b5026d9dad47adb Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Fri, 22 Jan 2021 12:12:59 -0500 Subject: [PATCH 80/83] [Fleet] Add `updateFleetRoleIfExists()` in order to update `fleet_enroll` permissions if role already exists (#88000) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/server/services/setup.ts | 41 ++++-- .../fleet_api_integration/apis/fleet_setup.ts | 124 ++++++++++++++++++ .../test/fleet_api_integration/apis/index.js | 2 + 3 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 x-pack/test/fleet_api_integration/apis/fleet_setup.ts diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 1ce7b1d85c8e4..0dcdfeb7b3801 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -59,6 +59,7 @@ async function createSetupSideEffects( ensureInstalledDefaultPackages(soClient, callCluster), outputService.ensureDefaultOutput(soClient), agentPolicyService.ensureDefaultAgentPolicy(soClient, esClient), + updateFleetRoleIfExists(callCluster), settingsService.getSettings(soClient).catch((e: any) => { if (e.isBoom && e.output.statusCode === 404) { const defaultSettings = createDefaultSettings(); @@ -126,15 +127,25 @@ async function createSetupSideEffects( return { isIntialized: true }; } -export async function setupFleet( - soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - callCluster: CallESAsCurrentUser, - options?: { forceRecreate?: boolean } -) { - // Create fleet_enroll role - // This should be done directly in ES at some point - const res = await callCluster('transport.request', { +async function updateFleetRoleIfExists(callCluster: CallESAsCurrentUser) { + try { + await callCluster('transport.request', { + method: 'GET', + path: `/_security/role/${FLEET_ENROLL_ROLE}`, + }); + } catch (e) { + if (e.status === 404) { + return; + } + + throw e; + } + + return putFleetRole(callCluster); +} + +async function putFleetRole(callCluster: CallESAsCurrentUser) { + return callCluster('transport.request', { method: 'PUT', path: `/_security/role/${FLEET_ENROLL_ROLE}`, body: { @@ -156,6 +167,18 @@ export async function setupFleet( ], }, }); +} + +export async function setupFleet( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + callCluster: CallESAsCurrentUser, + options?: { forceRecreate?: boolean } +) { + // Create fleet_enroll role + // This should be done directly in ES at some point + const res = await putFleetRole(callCluster); + // If the role is already created skip the rest unless you have forceRecreate set to true if (options?.forceRecreate !== true && res.role.created === false) { return; diff --git a/x-pack/test/fleet_api_integration/apis/fleet_setup.ts b/x-pack/test/fleet_api_integration/apis/fleet_setup.ts new file mode 100644 index 0000000000000..8e9a01b28ea9b --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fleet_setup.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const es = getService('es'); + + describe('fleet_setup', () => { + skipIfNoDockerRegistry(providerContext); + beforeEach(async () => { + try { + await es.security.deleteUser({ + username: 'fleet_enroll', + }); + } catch (e) { + if (e.meta?.statusCode !== 404) { + throw e; + } + } + try { + await es.security.deleteRole({ + name: 'fleet_enroll', + }); + } catch (e) { + if (e.meta?.statusCode !== 404) { + throw e; + } + } + }); + + it('should not create a fleet_enroll role if one does not already exist', async () => { + const { body: apiResponse } = await supertest + .post(`/api/fleet/setup`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + + expect(apiResponse.isInitialized).to.be(true); + + try { + await es.security.getUser({ + username: 'fleet_enroll', + }); + } catch (e) { + expect(e.meta?.statusCode).to.eql(404); + } + }); + + it('should update the fleet_enroll role with new index permissions if one does already exist', async () => { + try { + await es.security.putRole({ + name: 'fleet_enroll', + body: { + cluster: ['monitor', 'manage_api_key'], + indices: [ + { + names: [ + 'logs-*', + 'metrics-*', + 'traces-*', + '.ds-logs-*', + '.ds-metrics-*', + '.ds-traces-*', + ], + privileges: ['write', 'create_index', 'indices:admin/auto_create'], + allow_restricted_indices: false, + }, + ], + applications: [], + run_as: [], + metadata: {}, + transient_metadata: { enabled: true }, + }, + }); + } catch (e) { + if (e.meta?.statusCode !== 404) { + throw e; + } + } + + const { body: apiResponse } = await supertest + .post(`/api/fleet/setup`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + + expect(apiResponse.isInitialized).to.be(true); + + const { body: roleResponse } = await es.security.getRole({ + name: 'fleet_enroll', + }); + expect(roleResponse).to.have.key('fleet_enroll'); + expect(roleResponse.fleet_enroll).to.eql({ + cluster: ['monitor', 'manage_api_key'], + indices: [ + { + names: [ + 'logs-*', + 'metrics-*', + 'traces-*', + '.ds-logs-*', + '.ds-metrics-*', + '.ds-traces-*', + '.logs-endpoint.diagnostic.collection-*', + '.ds-.logs-endpoint.diagnostic.collection-*', + ], + privileges: ['write', 'create_index', 'indices:admin/auto_create'], + allow_restricted_indices: false, + }, + ], + applications: [], + run_as: [], + metadata: {}, + transient_metadata: { enabled: true }, + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/index.js b/x-pack/test/fleet_api_integration/apis/index.js index 0d634f60e282f..f472599652224 100644 --- a/x-pack/test/fleet_api_integration/apis/index.js +++ b/x-pack/test/fleet_api_integration/apis/index.js @@ -7,6 +7,8 @@ export default function ({ loadTestFile }) { describe('Fleet Endpoints', function () { this.tags('ciGroup10'); + // Fleet setup + loadTestFile(require.resolve('./fleet_setup')); // Agent setup loadTestFile(require.resolve('./agents_setup')); // Agents From d81ab83c1683072e4df812db4056dd2fdb382016 Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Fri, 22 Jan 2021 19:52:47 +0100 Subject: [PATCH 81/83] [examples] expressions explorer (#88344) --- ...-public.expressionsinspectoradapter.ast.md | 11 ++ ...blic.expressionsinspectoradapter.logast.md | 22 ++++ ...ions-public.expressionsinspectoradapter.md | 24 ++++ ...ibana-plugin-plugins-expressions-public.md | 1 + examples/expressions_explorer/README.md | 8 ++ examples/expressions_explorer/kibana.json | 10 ++ .../public/actions/navigate_action.ts | 21 ++++ .../public/actions/navigate_trigger.ts | 15 +++ .../public/actions_and_expressions.tsx | 102 +++++++++++++++ examples/expressions_explorer/public/app.tsx | 78 ++++++++++++ .../public/editor/expression_editor.tsx | 35 ++++++ .../public/functions/button.ts | 50 ++++++++ examples/expressions_explorer/public/index.ts | 11 ++ .../public/inspector/ast_debug_view.tsx | 78 ++++++++++++ .../inspector/expressions_inspector_view.tsx | 98 +++++++++++++++ .../expressions_inspector_view_wrapper.tsx | 17 +++ .../public/inspector/index.ts | 25 ++++ .../expressions_explorer/public/plugin.tsx | 87 +++++++++++++ .../public/render_expressions.tsx | 99 +++++++++++++++ .../public/renderers/button.tsx | 40 ++++++ .../public/run_expressions.tsx | 118 ++++++++++++++++++ examples/expressions_explorer/tsconfig.json | 18 +++ .../expressions/common/execution/execution.ts | 5 + .../util/expressions_inspector_adapter.ts | 22 ++++ src/plugins/expressions/common/util/index.ts | 1 + src/plugins/expressions/public/index.ts | 1 + src/plugins/expressions/public/public.api.md | 10 ++ test/examples/config.js | 1 + .../expressions_explorer/expressions.ts | 44 +++++++ test/examples/expressions_explorer/index.ts | 28 +++++ .../canvas_plugin_src/renderers/debug.tsx | 8 +- 31 files changed, 1085 insertions(+), 3 deletions(-) create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.ast.md create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.logast.md create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.md create mode 100644 examples/expressions_explorer/README.md create mode 100644 examples/expressions_explorer/kibana.json create mode 100644 examples/expressions_explorer/public/actions/navigate_action.ts create mode 100644 examples/expressions_explorer/public/actions/navigate_trigger.ts create mode 100644 examples/expressions_explorer/public/actions_and_expressions.tsx create mode 100644 examples/expressions_explorer/public/app.tsx create mode 100644 examples/expressions_explorer/public/editor/expression_editor.tsx create mode 100644 examples/expressions_explorer/public/functions/button.ts create mode 100644 examples/expressions_explorer/public/index.ts create mode 100644 examples/expressions_explorer/public/inspector/ast_debug_view.tsx create mode 100644 examples/expressions_explorer/public/inspector/expressions_inspector_view.tsx create mode 100644 examples/expressions_explorer/public/inspector/expressions_inspector_view_wrapper.tsx create mode 100644 examples/expressions_explorer/public/inspector/index.ts create mode 100644 examples/expressions_explorer/public/plugin.tsx create mode 100644 examples/expressions_explorer/public/render_expressions.tsx create mode 100644 examples/expressions_explorer/public/renderers/button.tsx create mode 100644 examples/expressions_explorer/public/run_expressions.tsx create mode 100644 examples/expressions_explorer/tsconfig.json create mode 100644 src/plugins/expressions/common/util/expressions_inspector_adapter.ts create mode 100644 test/examples/expressions_explorer/expressions.ts create mode 100644 test/examples/expressions_explorer/index.ts diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.ast.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.ast.md new file mode 100644 index 0000000000000..0fdf36bc719ec --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.ast.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionsInspectorAdapter](./kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.md) > [ast](./kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.ast.md) + +## ExpressionsInspectorAdapter.ast property + +Signature: + +```typescript +get ast(): any; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.logast.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.logast.md new file mode 100644 index 0000000000000..671270a5c78ce --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.logast.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionsInspectorAdapter](./kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.md) > [logAST](./kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.logast.md) + +## ExpressionsInspectorAdapter.logAST() method + +Signature: + +```typescript +logAST(ast: any): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ast | any | | + +Returns: + +`void` + diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.md new file mode 100644 index 0000000000000..23d542a0f69eb --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionsInspectorAdapter](./kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.md) + +## ExpressionsInspectorAdapter class + +Signature: + +```typescript +export declare class ExpressionsInspectorAdapter extends EventEmitter +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [ast](./kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.ast.md) | | any | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [logAST(ast)](./kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.logast.md) | | | + diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md index 1b97c9e11f83c..e3eb7a34175ee 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md @@ -16,6 +16,7 @@ | [ExpressionRenderer](./kibana-plugin-plugins-expressions-public.expressionrenderer.md) | | | [ExpressionRendererRegistry](./kibana-plugin-plugins-expressions-public.expressionrendererregistry.md) | | | [ExpressionRenderHandler](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.md) | | +| [ExpressionsInspectorAdapter](./kibana-plugin-plugins-expressions-public.expressionsinspectoradapter.md) | | | [ExpressionsPublicPlugin](./kibana-plugin-plugins-expressions-public.expressionspublicplugin.md) | | | [ExpressionsService](./kibana-plugin-plugins-expressions-public.expressionsservice.md) | ExpressionsService class is used for multiple purposes:1. It implements the same Expressions service that can be used on both: (1) server-side and (2) browser-side. 2. It implements the same Expressions service that users can fork/clone, thus have their own instance of the Expressions plugin. 3. ExpressionsService defines the public contracts of \*setup\* and \*start\* Kibana Platform life-cycles for ease-of-use on server-side and browser-side. 4. ExpressionsService creates a bound version of all exported contract functions. 5. Functions are bound the way there are:\`\`\`ts registerFunction = (...args: Parameters<Executor\['registerFunction'\]> ): ReturnType<Executor\['registerFunction'\]> => this.executor.registerFunction(...args); \`\`\`so that JSDoc appears in developers IDE when they use those plugins.expressions.registerFunction(. | | [ExpressionType](./kibana-plugin-plugins-expressions-public.expressiontype.md) | | diff --git a/examples/expressions_explorer/README.md b/examples/expressions_explorer/README.md new file mode 100644 index 0000000000000..ead0ca758f8e5 --- /dev/null +++ b/examples/expressions_explorer/README.md @@ -0,0 +1,8 @@ +## expressions explorer + +This example expressions explorer app shows how to: + - to run expression + - to render expression output + - emit events from expression renderer and handle them + +To run this example, use the command `yarn start --run-examples`. \ No newline at end of file diff --git a/examples/expressions_explorer/kibana.json b/examples/expressions_explorer/kibana.json new file mode 100644 index 0000000000000..038b7eea0ef21 --- /dev/null +++ b/examples/expressions_explorer/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "expressionsExplorer", + "version": "0.0.1", + "kibanaVersion": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["expressions", "inspector", "uiActions", "developerExamples"], + "optionalPlugins": [], + "requiredBundles": [] +} diff --git a/examples/expressions_explorer/public/actions/navigate_action.ts b/examples/expressions_explorer/public/actions/navigate_action.ts new file mode 100644 index 0000000000000..d29a9e6b345b6 --- /dev/null +++ b/examples/expressions_explorer/public/actions/navigate_action.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { createAction } from '../../../../src/plugins/ui_actions/public'; + +export const ACTION_NAVIGATE = 'ACTION_NAVIGATE'; + +export const createNavigateAction = () => + createAction({ + id: ACTION_NAVIGATE, + type: ACTION_NAVIGATE, + getDisplayName: () => 'Navigate', + execute: async (event: any) => { + window.location.href = event.href; + }, + }); diff --git a/examples/expressions_explorer/public/actions/navigate_trigger.ts b/examples/expressions_explorer/public/actions/navigate_trigger.ts new file mode 100644 index 0000000000000..eacbd968eaa93 --- /dev/null +++ b/examples/expressions_explorer/public/actions/navigate_trigger.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { Trigger } from '../../../../src/plugins/ui_actions/public'; + +export const NAVIGATE_TRIGGER_ID = 'NAVIGATE_TRIGGER_ID'; + +export const navigateTrigger: Trigger = { + id: NAVIGATE_TRIGGER_ID, +}; diff --git a/examples/expressions_explorer/public/actions_and_expressions.tsx b/examples/expressions_explorer/public/actions_and_expressions.tsx new file mode 100644 index 0000000000000..6e2eebcde4a0f --- /dev/null +++ b/examples/expressions_explorer/public/actions_and_expressions.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React, { useState } from 'react'; +import { + EuiFlexItem, + EuiFlexGroup, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiPanel, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { + ExpressionsStart, + ReactExpressionRenderer, + ExpressionsInspectorAdapter, +} from '../../../src/plugins/expressions/public'; +import { ExpressionEditor } from './editor/expression_editor'; +import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; +import { NAVIGATE_TRIGGER_ID } from './actions/navigate_trigger'; + +interface Props { + expressions: ExpressionsStart; + actions: UiActionsStart; +} + +export function ActionsExpressionsExample({ expressions, actions }: Props) { + const [expression, updateExpression] = useState( + 'button name="click me" href="http://www.google.com"' + ); + + const expressionChanged = (value: string) => { + updateExpression(value); + }; + + const inspectorAdapters = { + expression: new ExpressionsInspectorAdapter(), + }; + + const handleEvents = (event: any) => { + if (event.id !== 'NAVIGATE') return; + // enrich event context with some extra data + event.baseUrl = 'http://www.google.com'; + + actions.executeTriggerActions(NAVIGATE_TRIGGER_ID, event.value); + }; + + return ( + + + + +

Actions from expression renderers

+ + + + + + + + + Here you can play with sample `button` which takes a url as configuration and + displays a button which emits custom BUTTON_CLICK trigger to which we have attached + a custom action which performs the navigation. + + + + + + + + + + + + + { + return
{message}
; + }} + /> +
+
+
+
+
+ + ); +} diff --git a/examples/expressions_explorer/public/app.tsx b/examples/expressions_explorer/public/app.tsx new file mode 100644 index 0000000000000..d72cf08128a5a --- /dev/null +++ b/examples/expressions_explorer/public/app.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { + EuiPage, + EuiPageHeader, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiSpacer, + EuiText, + EuiLink, +} from '@elastic/eui'; +import { AppMountParameters } from '../../../src/core/public'; +import { ExpressionsStart } from '../../../src/plugins/expressions/public'; +import { Start as InspectorStart } from '../../../src/plugins/inspector/public'; +import { RunExpressionsExample } from './run_expressions'; +import { RenderExpressionsExample } from './render_expressions'; +import { ActionsExpressionsExample } from './actions_and_expressions'; +import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; + +interface Props { + expressions: ExpressionsStart; + inspector: InspectorStart; + actions: UiActionsStart; +} + +const ExpressionsExplorer = ({ expressions, inspector, actions }: Props) => { + return ( + + + Expressions Explorer + + + +

+ There are a couple of ways to run the expressions. Below some of the options are + demonstrated. You can read more about it{' '} + + here + +

+
+ + + + + + + + + + + + +
+
+
+
+ ); +}; + +export const renderApp = (props: Props, { element }: AppMountParameters) => { + ReactDOM.render(, element); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/examples/expressions_explorer/public/editor/expression_editor.tsx b/examples/expressions_explorer/public/editor/expression_editor.tsx new file mode 100644 index 0000000000000..e3dbb5998b92e --- /dev/null +++ b/examples/expressions_explorer/public/editor/expression_editor.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React from 'react'; +import { EuiCodeEditor } from '@elastic/eui'; + +interface Props { + value: string; + onChange: (value: string) => void; +} + +export function ExpressionEditor({ value, onChange }: Props) { + return ( + {}} + aria-label="Code Editor" + /> + ); +} diff --git a/examples/expressions_explorer/public/functions/button.ts b/examples/expressions_explorer/public/functions/button.ts new file mode 100644 index 0000000000000..8c39aa2743b30 --- /dev/null +++ b/examples/expressions_explorer/public/functions/button.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from '../../../../src/plugins/expressions/common'; + +interface Arguments { + href: string; + name: string; +} + +export type ExpressionFunctionButton = ExpressionFunctionDefinition< + 'button', + unknown, + Arguments, + unknown +>; + +export const buttonFn: ExpressionFunctionButton = { + name: 'button', + args: { + href: { + help: i18n.translate('expressions.functions.font.args.href', { + defaultMessage: 'Link to which to navigate', + }), + types: ['string'], + required: true, + }, + name: { + help: i18n.translate('expressions.functions.font.args.name', { + defaultMessage: 'Name of the button', + }), + types: ['string'], + default: 'button', + }, + }, + help: 'Configures the button', + fn: (input: unknown, args: Arguments) => { + return { + type: 'render', + as: 'button', + value: args, + }; + }, +}; diff --git a/examples/expressions_explorer/public/index.ts b/examples/expressions_explorer/public/index.ts new file mode 100644 index 0000000000000..a6dbbc9198f44 --- /dev/null +++ b/examples/expressions_explorer/public/index.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { ExpressionsExplorerPlugin } from './plugin'; + +export const plugin = () => new ExpressionsExplorerPlugin(); diff --git a/examples/expressions_explorer/public/inspector/ast_debug_view.tsx b/examples/expressions_explorer/public/inspector/ast_debug_view.tsx new file mode 100644 index 0000000000000..d860ff30bd8e9 --- /dev/null +++ b/examples/expressions_explorer/public/inspector/ast_debug_view.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React, { useState } from 'react'; +import { EuiTreeView, EuiDescriptionList, EuiCodeBlock, EuiText, EuiSpacer } from '@elastic/eui'; + +interface Props { + ast: any; +} + +const decorateAst = (ast: any, nodeClicked: any) => { + return ast.chain.map((link: any) => { + return { + id: link.function + Math.random(), + label: link.function, + callback: () => { + nodeClicked(link.debug); + }, + children: Object.keys(link.arguments).reduce((result: any, key: string) => { + if (typeof link.arguments[key] === 'object') { + // result[key] = decorateAst(link.arguments[key]); + } + return result; + }, []), + }; + }); +}; + +const prepareNode = (key: string, value: any) => { + if (key === 'args') { + return ( + + {JSON.stringify(value, null, '\t')} + + ); + } else if (key === 'output' || key === 'input') { + return ( + + {JSON.stringify(value, null, '\t')} + + ); + } else if (key === 'success') { + return value ? 'true' : 'false'; + } else return {value}; +}; + +export function AstDebugView({ ast }: Props) { + const [nodeInfo, setNodeInfo] = useState([] as any[]); + const items = decorateAst(ast, (node: any) => { + setNodeInfo( + Object.keys(node).map((key) => ({ + title: key, + description: prepareNode(key, node[key]), + })) + ); + }); + + return ( +
+ List of executed expression functions: + + + Details of selected function: + +
+ ); +} diff --git a/examples/expressions_explorer/public/inspector/expressions_inspector_view.tsx b/examples/expressions_explorer/public/inspector/expressions_inspector_view.tsx new file mode 100644 index 0000000000000..1233735072d04 --- /dev/null +++ b/examples/expressions_explorer/public/inspector/expressions_inspector_view.tsx @@ -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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiEmptyPrompt } from '@elastic/eui'; +import { InspectorViewProps, Adapters } from '../../../../src/plugins/inspector/public'; +import { AstDebugView } from './ast_debug_view'; + +interface ExpressionsInspectorViewComponentState { + ast: any; + adapters: Adapters; +} + +class ExpressionsInspectorViewComponent extends Component< + InspectorViewProps, + ExpressionsInspectorViewComponentState +> { + static propTypes = { + adapters: PropTypes.object.isRequired, + title: PropTypes.string.isRequired, + }; + + state = {} as ExpressionsInspectorViewComponentState; + + static getDerivedStateFromProps( + nextProps: Readonly, + state: ExpressionsInspectorViewComponentState + ) { + if (state && nextProps.adapters === state.adapters) { + return null; + } + + const { ast } = nextProps.adapters.expression; + + return { + adapters: nextProps.adapters, + ast, + }; + } + + onUpdateData = (ast: any) => { + this.setState({ + ast, + }); + }; + + componentDidMount() { + this.props.adapters.expression!.on('change', this.onUpdateData); + } + + componentWillUnmount() { + this.props.adapters.expression!.removeListener('change', this.onUpdateData); + } + + static renderNoData() { + return ( + + + + } + body={ + +

+ +

+
+ } + /> + ); + } + + render() { + if (!this.state.ast) { + return ExpressionsInspectorViewComponent.renderNoData(); + } + + return ; + } +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export default ExpressionsInspectorViewComponent; diff --git a/examples/expressions_explorer/public/inspector/expressions_inspector_view_wrapper.tsx b/examples/expressions_explorer/public/inspector/expressions_inspector_view_wrapper.tsx new file mode 100644 index 0000000000000..b10c82e5df309 --- /dev/null +++ b/examples/expressions_explorer/public/inspector/expressions_inspector_view_wrapper.tsx @@ -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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React, { lazy } from 'react'; + +const ExpressionsInspectorViewComponent = lazy(() => import('./expressions_inspector_view')); + +export const getExpressionsInspectorViewComponentWrapper = () => { + return (props: any) => { + return ; + }; +}; diff --git a/examples/expressions_explorer/public/inspector/index.ts b/examples/expressions_explorer/public/inspector/index.ts new file mode 100644 index 0000000000000..ec87a1240ac74 --- /dev/null +++ b/examples/expressions_explorer/public/inspector/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { Adapters, InspectorViewDescription } from '../../../../src/plugins/inspector/public'; +import { getExpressionsInspectorViewComponentWrapper } from './expressions_inspector_view_wrapper'; + +export const getExpressionsInspectorViewDescription = (): InspectorViewDescription => ({ + title: i18n.translate('data.inspector.table.dataTitle', { + defaultMessage: 'Expression', + }), + order: 100, + help: i18n.translate('data.inspector.table..dataDescriptionTooltip', { + defaultMessage: 'View the expression behind the visualization', + }), + shouldShow(adapters: Adapters) { + return Boolean(adapters.expression); + }, + component: getExpressionsInspectorViewComponentWrapper(), +}); diff --git a/examples/expressions_explorer/public/plugin.tsx b/examples/expressions_explorer/public/plugin.tsx new file mode 100644 index 0000000000000..9643389ad881c --- /dev/null +++ b/examples/expressions_explorer/public/plugin.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public'; +import { DeveloperExamplesSetup } from '../../developer_examples/public'; +import { ExpressionsSetup, ExpressionsStart } from '../../../src/plugins/expressions/public'; +import { + Setup as InspectorSetup, + Start as InspectorStart, +} from '../../../src/plugins/inspector/public'; +import { getExpressionsInspectorViewDescription } from './inspector'; +import { UiActionsStart, UiActionsSetup } from '../../../src/plugins/ui_actions/public'; +import { NAVIGATE_TRIGGER_ID, navigateTrigger } from './actions/navigate_trigger'; +import { ACTION_NAVIGATE, createNavigateAction } from './actions/navigate_action'; +import { buttonRenderer } from './renderers/button'; +import { buttonFn } from './functions/button'; + +interface StartDeps { + expressions: ExpressionsStart; + inspector: InspectorStart; + uiActions: UiActionsStart; +} + +interface SetupDeps { + uiActions: UiActionsSetup; + expressions: ExpressionsSetup; + inspector: InspectorSetup; + developerExamples: DeveloperExamplesSetup; +} + +export class ExpressionsExplorerPlugin implements Plugin { + public setup(core: CoreSetup, deps: SetupDeps) { + // register custom inspector adapter & view + deps.inspector.registerView(getExpressionsInspectorViewDescription()); + + // register custom actions + deps.uiActions.registerTrigger(navigateTrigger); + deps.uiActions.registerAction(createNavigateAction()); + deps.uiActions.attachAction(NAVIGATE_TRIGGER_ID, ACTION_NAVIGATE); + + // register custom functions and renderers + deps.expressions.registerRenderer(buttonRenderer); + deps.expressions.registerFunction(buttonFn); + + core.application.register({ + id: 'expressionsExplorer', + title: 'Expressions Explorer', + navLinkStatus: AppNavLinkStatus.hidden, + async mount(params: AppMountParameters) { + const [, depsStart] = await core.getStartServices(); + const { renderApp } = await import('./app'); + return renderApp( + { + expressions: depsStart.expressions, + inspector: depsStart.inspector, + actions: depsStart.uiActions, + }, + params + ); + }, + }); + + deps.developerExamples.register({ + appId: 'expressionsExplorer', + title: 'Expressions', + description: `Expressions is a plugin that allows to execute Kibana expressions and render content using expression renderers. This example plugin showcases various usage scenarios.`, + links: [ + { + label: 'README', + href: 'https://github.com/elastic/kibana/blob/master/src/plugins/expressions/README.md', + iconType: 'logoGithub', + size: 's', + target: '_blank', + }, + ], + }); + } + + public start() {} + + public stop() {} +} diff --git a/examples/expressions_explorer/public/render_expressions.tsx b/examples/expressions_explorer/public/render_expressions.tsx new file mode 100644 index 0000000000000..ffbe558f30218 --- /dev/null +++ b/examples/expressions_explorer/public/render_expressions.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React, { useState } from 'react'; +import { + EuiFlexItem, + EuiFlexGroup, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiPanel, + EuiText, + EuiTitle, + EuiButton, +} from '@elastic/eui'; +import { + ExpressionsStart, + ReactExpressionRenderer, + ExpressionsInspectorAdapter, +} from '../../../src/plugins/expressions/public'; +import { ExpressionEditor } from './editor/expression_editor'; +import { Start as InspectorStart } from '../../../src/plugins/inspector/public'; + +interface Props { + expressions: ExpressionsStart; + inspector: InspectorStart; +} + +export function RenderExpressionsExample({ expressions, inspector }: Props) { + const [expression, updateExpression] = useState('markdown "## expressions explorer rendering"'); + + const expressionChanged = (value: string) => { + updateExpression(value); + }; + + const inspectorAdapters = { + expression: new ExpressionsInspectorAdapter(), + }; + + return ( + + + + +

Render expressions

+
+
+
+ + + + + + In the below editor you can enter your expression and render it. Using + ReactExpressionRenderer component makes that very easy. + + + + { + inspector.open(inspectorAdapters); + }} + > + Open Inspector + + + + + + + + + + + + + { + return
{message}
; + }} + /> +
+
+
+
+
+
+ ); +} diff --git a/examples/expressions_explorer/public/renderers/button.tsx b/examples/expressions_explorer/public/renderers/button.tsx new file mode 100644 index 0000000000000..32f1f31894dce --- /dev/null +++ b/examples/expressions_explorer/public/renderers/button.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import ReactDOM from 'react-dom'; +import React from 'react'; +import { EuiButton } from '@elastic/eui'; +import { ExpressionRenderDefinition } from '../../../../src/plugins/expressions/common/expression_renderers'; + +export const buttonRenderer: ExpressionRenderDefinition = { + name: 'button', + displayName: 'Button', + reuseDomNode: true, + render(domNode, config, handlers) { + const buttonClick = () => { + handlers.event({ + id: 'NAVIGATE', + value: { + href: config.href, + }, + }); + }; + + const renderDebug = () => ( +
+ + {config.name} + +
+ ); + + ReactDOM.render(renderDebug(), domNode, () => handlers.done()); + + handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); + }, +}; diff --git a/examples/expressions_explorer/public/run_expressions.tsx b/examples/expressions_explorer/public/run_expressions.tsx new file mode 100644 index 0000000000000..efbdbc2d41836 --- /dev/null +++ b/examples/expressions_explorer/public/run_expressions.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React, { useState, useEffect, useMemo } from 'react'; +import { + EuiCodeBlock, + EuiFlexItem, + EuiFlexGroup, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiPanel, + EuiText, + EuiTitle, + EuiButton, +} from '@elastic/eui'; +import { + ExpressionsStart, + ExpressionsInspectorAdapter, +} from '../../../src/plugins/expressions/public'; +import { ExpressionEditor } from './editor/expression_editor'; +import { Start as InspectorStart } from '../../../src/plugins/inspector/public'; + +interface Props { + expressions: ExpressionsStart; + inspector: InspectorStart; +} + +export function RunExpressionsExample({ expressions, inspector }: Props) { + const [expression, updateExpression] = useState('markdown "## expressions explorer"'); + const [result, updateResult] = useState({}); + + const expressionChanged = (value: string) => { + updateExpression(value); + }; + + const inspectorAdapters = useMemo( + () => ({ + expression: new ExpressionsInspectorAdapter(), + }), + [] + ); + + useEffect(() => { + const runExpression = async () => { + const execution = expressions.execute(expression, null, { + debug: true, + inspectorAdapters, + }); + + const data: any = await execution.getData(); + updateResult(data); + }; + + runExpression(); + }, [expression, expressions, inspectorAdapters]); + + return ( + + + + +

Run expressions

+
+
+
+ + + + + + In the below editor you can enter your expression and execute it. Using + expressions.execute allows you to easily run the expression. + + + + { + inspector.open(inspectorAdapters); + }} + > + Open Inspector + + + + + + + + + + + + + + {JSON.stringify(result, null, '\t')} + + + + + + +
+ ); +} diff --git a/examples/expressions_explorer/tsconfig.json b/examples/expressions_explorer/tsconfig.json new file mode 100644 index 0000000000000..b4449819b25a6 --- /dev/null +++ b/examples/expressions_explorer/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../typings/**/*", + ], + "exclude": [], + "references": [ + { "path": "../../src/core/tsconfig.json" }, + { "path": "../../src/plugins/kibana_react/tsconfig.json" }, + ] +} diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index 8e068818ec0ce..0240ec90cb1e6 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -29,6 +29,7 @@ import { getByAlias } from '../util/get_by_alias'; import { ExecutionContract } from './execution_contract'; import { ExpressionExecutionParams } from '../service'; import { TablesAdapter } from '../util/tables_adapter'; +import { ExpressionsInspectorAdapter } from '../util/expressions_inspector_adapter'; /** * AbortController is not available in Node until v15, so we @@ -63,6 +64,7 @@ export interface ExecutionParams { const createDefaultInspectorAdapters = (): DefaultInspectorAdapters => ({ requests: new RequestAdapter(), tables: new TablesAdapter(), + expression: new ExpressionsInspectorAdapter(), }); export class Execution< @@ -208,6 +210,9 @@ export class Execution< this.firstResultFuture.promise .then( (result) => { + if (this.context.inspectorAdapters.expression) { + this.context.inspectorAdapters.expression.logAST(this.state.get().ast); + } this.state.transitions.setResult(result); }, (error) => { diff --git a/src/plugins/expressions/common/util/expressions_inspector_adapter.ts b/src/plugins/expressions/common/util/expressions_inspector_adapter.ts new file mode 100644 index 0000000000000..c82884d373d2f --- /dev/null +++ b/src/plugins/expressions/common/util/expressions_inspector_adapter.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { EventEmitter } from 'events'; + +export class ExpressionsInspectorAdapter extends EventEmitter { + private _ast: any = {}; + + public logAST(ast: any): void { + this._ast = ast; + this.emit('change', this._ast); + } + + public get ast() { + return this._ast; + } +} diff --git a/src/plugins/expressions/common/util/index.ts b/src/plugins/expressions/common/util/index.ts index ecb7d5cdca81e..4762f9979fe4a 100644 --- a/src/plugins/expressions/common/util/index.ts +++ b/src/plugins/expressions/common/util/index.ts @@ -9,3 +9,4 @@ export * from './create_error'; export * from './get_by_alias'; export * from './tables_adapter'; +export * from './expressions_inspector_adapter'; diff --git a/src/plugins/expressions/public/index.ts b/src/plugins/expressions/public/index.ts index 9485daf49c981..d6dd2fc1f3d37 100644 --- a/src/plugins/expressions/public/index.ts +++ b/src/plugins/expressions/public/index.ts @@ -107,4 +107,5 @@ export { ExpressionsServiceSetup, ExpressionsServiceStart, TablesAdapter, + ExpressionsInspectorAdapter, } from '../common'; diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 7fa0857be8aba..029d727e82e74 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -551,6 +551,16 @@ export class ExpressionRenderHandler { update$: Observable; } +// Warning: (ae-missing-release-tag) "ExpressionsInspectorAdapter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class ExpressionsInspectorAdapter extends EventEmitter { + // (undocumented) + get ast(): any; + // (undocumented) + logAST(ast: any): void; +} + // Warning: (ae-missing-release-tag) "ExpressionsPublicPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/test/examples/config.js b/test/examples/config.js index a720899a637de..aab71cb305016 100644 --- a/test/examples/config.js +++ b/test/examples/config.js @@ -19,6 +19,7 @@ export default async function ({ readConfigFile }) { require.resolve('./ui_actions'), require.resolve('./state_sync'), require.resolve('./routing'), + require.resolve('./expressions_explorer'), ], services: { ...functionalConfig.get('services'), diff --git a/test/examples/expressions_explorer/expressions.ts b/test/examples/expressions_explorer/expressions.ts new file mode 100644 index 0000000000000..7261564e6db38 --- /dev/null +++ b/test/examples/expressions_explorer/expressions.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import expect from '@kbn/expect'; + +import { PluginFunctionalProviderContext } from 'test/plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: PluginFunctionalProviderContext) { + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const browser = getService('browser'); + + describe('', () => { + it('runs expression', async () => { + await retry.try(async () => { + const text = await testSubjects.getVisibleText('expressionResult'); + expect(text).to.be( + '{\n "type": "error",\n "error": {\n "message": "Function markdown could not be found.",\n "name": "fn not found"\n }\n}' + ); + }); + }); + + it('renders expression', async () => { + await retry.try(async () => { + const text = await testSubjects.getVisibleText('expressionRender'); + expect(text).to.be('Function markdown could not be found.'); + }); + }); + + it('emits an action and navigates', async () => { + await testSubjects.click('testExpressionButton'); + await retry.try(async () => { + const text = await browser.getCurrentUrl(); + expect(text).to.be('https://www.google.com/?gws_rd=ssl'); + }); + }); + }); +} diff --git a/test/examples/expressions_explorer/index.ts b/test/examples/expressions_explorer/index.ts new file mode 100644 index 0000000000000..77d2a594c0f29 --- /dev/null +++ b/test/examples/expressions_explorer/index.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { PluginFunctionalProviderContext } from 'test/plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ + getService, + getPageObjects, + loadTestFile, +}: PluginFunctionalProviderContext) { + const browser = getService('browser'); + const PageObjects = getPageObjects(['common', 'header']); + + describe('expressions explorer', function () { + before(async () => { + await browser.setWindowSize(1300, 900); + await PageObjects.common.navigateToApp('expressionsExplorer'); + }); + + loadTestFile(require.resolve('./expressions')); + }); +} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.tsx index b4fbba96e8dfb..341913a033c05 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.tsx @@ -26,9 +26,11 @@ export const debug: RendererFactory = () => ({ ReactDOM.render(renderDebug(), domNode, () => handlers.done()); - handlers.onResize(() => { - ReactDOM.render(renderDebug(), domNode, () => handlers.done()); - }); + if (handlers.onResize) { + handlers.onResize(() => { + ReactDOM.render(renderDebug(), domNode, () => handlers.done()); + }); + } handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); }, From 8263d47d378315bdcad990620010c8452f3133d1 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Fri, 22 Jan 2021 14:19:09 -0500 Subject: [PATCH 82/83] Fix sharing saved objects phase 2 CI (#89056) --- .../migrations/core/document_migrator.test.ts | 14 ++++++++++++++ .../migrations/core/document_migrator.ts | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts index 741f715ba6ebe..6ba652abda3d5 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts @@ -206,6 +206,20 @@ describe('DocumentMigrator', () => { ); }); + it('coerces the current Kibana version if it has a hyphen', () => { + const validDefinition = { + kibanaVersion: '3.2.0-SNAPSHOT', + typeRegistry: createRegistry({ + name: 'foo', + convertToMultiNamespaceTypeVersion: '3.2.0', + namespaceType: 'multiple', + }), + minimumConvertVersion: '0.0.0', + log: mockLogger, + }; + expect(() => new DocumentMigrator(validDefinition)).not.toThrowError(); + }); + it('validates convertToMultiNamespaceTypeVersion is not used on a patch version', () => { const invalidDefinition = { kibanaVersion: '3.2.3', diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.ts b/src/core/server/saved_objects/migrations/core/document_migrator.ts index e4b89a949d3cf..e93586ec7ce4c 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -159,10 +159,11 @@ export class DocumentMigrator implements VersionedTransformer { */ constructor({ typeRegistry, - kibanaVersion, + kibanaVersion: rawKibanaVersion, minimumConvertVersion = DEFAULT_MINIMUM_CONVERT_VERSION, log, }: DocumentMigratorOptions) { + const kibanaVersion = rawKibanaVersion.split('-')[0]; // coerce a semver-like string (x.y.z-SNAPSHOT) or prerelease version (x.y.z-alpha) to a regular semver (x.y.z) validateMigrationDefinition(typeRegistry, kibanaVersion, minimumConvertVersion); this.documentMigratorOptions = { typeRegistry, kibanaVersion, log }; From c739f437ddcf48af1fb5719f0034fe1ef45f31b0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Jan 2021 10:36:21 +0200 Subject: [PATCH 83/83] Update dependency vega to ^5.19.1 (#88984) Co-authored-by: Renovate Bot Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 60 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 87e0f84695235..2fdc31820b9d4 100644 --- a/package.json +++ b/package.json @@ -828,7 +828,7 @@ "url-loader": "^2.2.0", "use-resize-observer": "^6.0.0", "val-loader": "^1.1.1", - "vega": "^5.18.0", + "vega": "^5.19.1", "vega-lite": "^4.17.0", "vega-schema-url-parser": "^2.1.0", "vega-tooltip": "^0.25.0", diff --git a/yarn.lock b/yarn.lock index cc32349b10860..828a3b630a838 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28645,10 +28645,10 @@ vega-functions@^5.10.0: vega-time "^2.0.4" vega-util "^1.16.0" -vega-functions@~5.11.0: - version "5.11.0" - resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.11.0.tgz#a590d016f93c81730bdbc336b377231d7ae48569" - integrity sha512-/p0QIDiA3RaUZ7drxHuClpDQCrIScSHJlY0oo0+GFYGfp+lvb29Ox1T4a+wtqeCp6NRaTWry+EwDxojnshTZIQ== +vega-functions@^5.12.0, vega-functions@~5.12.0: + version "5.12.0" + resolved "https://registry.yarnpkg.com/vega-functions/-/vega-functions-5.12.0.tgz#44bf08a7b20673dc8cf51d6781c8ea1399501668" + integrity sha512-3hljmGs+gR7TbO/yYuvAP9P5laKISf1GKk4yRHLNdM61fWgKm8pI3f6LY2Hvq9cHQFTiJ3/5/Bx2p1SX5R4quQ== dependencies: d3-array "^2.7.1" d3-color "^2.0.0" @@ -28656,8 +28656,8 @@ vega-functions@~5.11.0: vega-dataflow "^5.7.3" vega-expression "^4.0.1" vega-scale "^7.1.1" - vega-scenegraph "^4.9.2" - vega-selections "^5.2.0" + vega-scenegraph "^4.9.3" + vega-selections "^5.3.0" vega-statistics "^1.7.9" vega-time "^2.0.4" vega-util "^1.16.0" @@ -28724,16 +28724,16 @@ vega-loader@^4.3.2, vega-loader@^4.3.3, vega-loader@~4.4.0: vega-format "^1.0.4" vega-util "^1.16.0" -vega-parser@~6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.1.2.tgz#7f25751177e38c3239560a9c427ded8d2ba617bb" - integrity sha512-aGyZrNzPrBruEb/WhemKDuDjQsIkMDGIgnSJci0b+9ZVxjyAzMl7UfGbiYorPiJlnIercjUJbMoFD6fCIf4gqQ== +vega-parser@~6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/vega-parser/-/vega-parser-6.1.3.tgz#df72785e4b086eceb90ee6219a399210933b507b" + integrity sha512-8oiVhhW26GQ4GZBvolId8FVFvhn3s1KGgPlD7Z+4P2wkV+xe5Nqu0TEJ20F/cn3b88fd0Vj48X3BH3dlSeKNFg== dependencies: vega-dataflow "^5.7.3" vega-event-selector "^2.0.6" - vega-functions "^5.10.0" + vega-functions "^5.12.0" vega-scale "^7.1.1" - vega-util "^1.15.2" + vega-util "^1.16.0" vega-projection@^1.4.5, vega-projection@~1.4.5: version "1.4.5" @@ -28772,7 +28772,7 @@ vega-scale@^7.0.3, vega-scale@^7.1.1, vega-scale@~7.1.1: vega-time "^2.0.4" vega-util "^1.15.2" -vega-scenegraph@^4.9.2, vega-scenegraph@~4.9.2: +vega-scenegraph@^4.9.2: version "4.9.2" resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.9.2.tgz#83b1dbc34a9ab5595c74d547d6d95849d74451ed" integrity sha512-epm1CxcB8AucXQlSDeFnmzy0FCj+HV2k9R6ch2lfLRln5lPLEfgJWgFcFhVf5jyheY0FSeHH52Q5zQn1vYI1Ow== @@ -28784,6 +28784,18 @@ vega-scenegraph@^4.9.2, vega-scenegraph@~4.9.2: vega-scale "^7.1.1" vega-util "^1.15.2" +vega-scenegraph@^4.9.3, vega-scenegraph@~4.9.3: + version "4.9.3" + resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-4.9.3.tgz#c4720550ea7ff5c8d9d0690f47fe2640547cfc6b" + integrity sha512-lBvqLbXqrqRCTGJmSgzZC/tLR/o+TXfakbdhDzNdpgTavTaQ65S/67Gpj5hPpi77DvsfZUIY9lCEeO37aJhy0Q== + dependencies: + d3-path "^2.0.0" + d3-shape "^2.0.0" + vega-canvas "^1.2.5" + vega-loader "^4.3.3" + vega-scale "^7.1.1" + vega-util "^1.15.2" + vega-schema-url-parser@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/vega-schema-url-parser/-/vega-schema-url-parser-2.1.0.tgz#847f9cf9f1624f36f8a51abc1adb41ebc6673cb4" @@ -28797,10 +28809,10 @@ vega-selections@^5.1.5: vega-expression "^4.0.0" vega-util "^1.15.2" -vega-selections@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/vega-selections/-/vega-selections-5.2.0.tgz#d85968d1bccc175fd92661c91d88151ffd5ade83" - integrity sha512-Xf3nTTJHRGw4tQMbt+0sBI/7WkEIzPG9E4HXkZk5Y9Q2HsGRVLmrAEXHSfpENrBLWTBZk/uvmP9rKDG7cbcTrg== +vega-selections@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/vega-selections/-/vega-selections-5.3.0.tgz#810f2e7b7642fa836cf98b2e5dcc151093b1f6a7" + integrity sha512-vC4NPsuN+IffruFXfH0L3i2A51RgG4PqpLv85TvrEAIYnSkyKDE4bf+wVraR3aPdnLLkc3+tYuMi6le5FmThIA== dependencies: vega-expression "^4.0.1" vega-util "^1.16.0" @@ -28899,10 +28911,10 @@ vega-wordcloud@~4.1.3: vega-statistics "^1.7.9" vega-util "^1.15.2" -vega@^5.18.0: - version "5.18.0" - resolved "https://registry.yarnpkg.com/vega/-/vega-5.18.0.tgz#98645e5d3bd5267d66ea3e701d99dcff63cfff8a" - integrity sha512-ysqouhboWNXSuQNN7W5IGOXsnEJNFVX5duCi0tTwRsFLc61FshpqVh4+4VoXg5pH0ZCxwpqbOwd2ULZWjJTx6g== +vega@^5.19.1: + version "5.19.1" + resolved "https://registry.yarnpkg.com/vega/-/vega-5.19.1.tgz#64c8350740fe1a11d56cc6617ab3a76811fd704c" + integrity sha512-UE6/c9q9kzuz4HULFuU9HscBASoZa+zcXqGKdbQP545Nwmhd078QpcH+wZsq9lYfiTxmFtzLK/a0OH0zhkghvA== dependencies: vega-crossfilter "~4.0.5" vega-dataflow "~5.7.3" @@ -28911,17 +28923,17 @@ vega@^5.18.0: vega-expression "~4.0.1" vega-force "~4.0.7" vega-format "~1.0.4" - vega-functions "~5.11.0" + vega-functions "~5.12.0" vega-geo "~4.3.8" vega-hierarchy "~4.0.9" vega-label "~1.0.0" vega-loader "~4.4.0" - vega-parser "~6.1.2" + vega-parser "~6.1.3" vega-projection "~1.4.5" vega-regression "~1.0.9" vega-runtime "~6.1.3" vega-scale "~7.1.1" - vega-scenegraph "~4.9.2" + vega-scenegraph "~4.9.3" vega-statistics "~1.7.9" vega-time "~2.0.4" vega-transforms "~4.9.3"

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%0DqU|vl=DQohkO&o$aw2QW3|*pSvUX!faEH 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