From 95ff10b47ae6b7c47b65ce7e38a7769373c68f7a Mon Sep 17 00:00:00 2001 From: Dimitris Athanasiou Date: Tue, 17 Nov 2020 12:48:25 +0200 Subject: [PATCH 01/10] [ML] Update console autocomplete for ML data frame evaluate API (#83151) We have added evaluation for all types of data frame analysis since the last update. This commit updates autocomplete accordingly. --- .../overrides/ml.evaluate_data_frame.json | 86 ++++++++++++++++--- 1 file changed, 73 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/overrides/ml.evaluate_data_frame.json b/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/overrides/ml.evaluate_data_frame.json index bf78cf7f09aaf..f6cc8bf2693ce 100644 --- a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/overrides/ml.evaluate_data_frame.json +++ b/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/overrides/ml.evaluate_data_frame.json @@ -1,23 +1,83 @@ { "ml.evaluate_data_frame": { "data_autocomplete_rules": { + "index": "", "evaluation": { - "actual_field": "FIELD_NAME", - "predicted_probability_field": "FIELD_NAME", - "metrics": { - "auc_roc": { - "include_curve": true + "__one_of": [ + { + "outlier_detection": { + "__template": { + "actual_field": "FIELD_NAME", + "predicted_probability_field": "FIELD_NAME" + }, + "actual_field": "FIELD_NAME", + "predicted_probability_field": "FIELD_NAME", + "metrics": { + "auc_roc": { + "include_curve": false + }, + "precision": { + "at": [] + }, + "recall": { + "at": [] + }, + "confusion_matrix": { + "at": [] + } + } + } }, - "precision": { - "at": [] + { + "regression": { + "__template": { + "actual_field": "FIELD_NAME", + "predicted_field": "FIELD_NAME" + }, + "actual_field": "FIELD_NAME", + "predicted_field": "FIELD_NAME", + "metrics": { + "mse": {}, + "msle": { + "offset": 1.0 + }, + "r_squared": {}, + "huber": { + "delta": 1.0 + } + } + } }, - "recall": { - "at": [] - }, - "confusion_matrix": { - "at": [] + { + "classification": { + "__template": { + "actual_field": "FIELD_NAME", + "predicted_field": "FIELD_NAME", + "top_classes_field": "FIELD_NAME" + }, + "actual_field": "FIELD_NAME", + "predicted_field": "FIELD_NAME", + "top_classes_field": "FIELD_NAME", + "metrics": { + "accuracy": {}, + "precision": {}, + "recall": {}, + "multiclass_confusion_matrix": { + "size": 10 + }, + "precision": {}, + "recall": {}, + "auc_roc": { + "__template": { + "class_name": "" + }, + "class_name": "", + "include_curve": false + } + } + } } - } + ] } } } From 48231c8400d81c8628313368e4bd90cf37864657 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 17 Nov 2020 14:28:41 +0300 Subject: [PATCH 02/10] remove headers timeout hack, rely on nodejs timeouts (#83419) --- src/core/server/http/http_tools.ts | 4 ---- test/server_integration/http/platform/headers.ts | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts index 14adbced95c7a..1e69669e080ec 100644 --- a/src/core/server/http/http_tools.ts +++ b/src/core/server/http/http_tools.ts @@ -103,10 +103,6 @@ interface ListenerOptions { export function createServer(serverOptions: ServerOptions, listenerOptions: ListenerOptions) { const server = new Server(serverOptions); - // remove fix + test as soon as update node.js to v12.19 https://github.com/elastic/kibana/pull/61587 - server.listener.headersTimeout = - listenerOptions.keepaliveTimeout + 2 * server.listener.headersTimeout; - server.listener.keepAliveTimeout = listenerOptions.keepaliveTimeout; server.listener.setTimeout(listenerOptions.socketTimeout); server.listener.on('timeout', (socket) => { diff --git a/test/server_integration/http/platform/headers.ts b/test/server_integration/http/platform/headers.ts index 260bc37bd1328..50cfa5c702231 100644 --- a/test/server_integration/http/platform/headers.ts +++ b/test/server_integration/http/platform/headers.ts @@ -31,7 +31,7 @@ export default function ({ getService }: FtrProviderContext) { const config = getService('config'); describe('headers timeout ', () => { - it('issue-73849', async () => { + it('handles correctly. See issue #73849', async () => { const agent = new Http.Agent({ keepAlive: true, }); @@ -74,7 +74,7 @@ export default function ({ getService }: FtrProviderContext) { } await performRequest(); - const defaultHeadersTimeout = 40 * oneSec; + const defaultHeadersTimeout = 60 * oneSec; await delay(defaultHeadersTimeout + oneSec); await performRequest(); }); From de8931546db005294805637285225f4c97dabc22 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 17 Nov 2020 13:06:51 +0100 Subject: [PATCH 03/10] [Uptime] Fix monitor list down histogram (#83411) --- .../server/lib/requests/get_monitor_states.ts | 2 +- .../monitor_states_real_data.snap | 369 ++++++++++++++++++ .../api_integration/apis/uptime/rest/index.ts | 3 + .../uptime/rest/monitor_states_real_data.ts | 11 + 4 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/api_integration/apis/uptime/rest/__snapshots__/monitor_states_real_data.snap diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts index 020fcf5331188..2ff1043d79e84 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts @@ -151,7 +151,7 @@ export const getHistogramForMonitors = async ( }, }, }; - const result = await queryContext.search(params); + const { body: result } = await queryContext.search(params); const histoBuckets: any[] = result.aggregations?.histogram.buckets ?? []; const simplified = histoBuckets.map((histoBucket: any): { timestamp: number; byId: any } => { 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 new file mode 100644 index 0000000000000..50625683b605d --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/__snapshots__/monitor_states_real_data.snap @@ -0,0 +1,369 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`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, + "summaries": Array [ + Object { + "histogram": Object { + "points": Array [ + Object { + "down": 1, + "timestamp": 1568172624744, + }, + Object { + "down": 2, + "timestamp": 1568172677247, + }, + Object { + "down": 1, + "timestamp": 1568172729750, + }, + Object { + "down": 2, + "timestamp": 1568172782253, + }, + Object { + "down": 2, + "timestamp": 1568172834756, + }, + Object { + "down": 2, + "timestamp": 1568172887259, + }, + Object { + "down": 1, + "timestamp": 1568172939762, + }, + Object { + "down": 2, + "timestamp": 1568172992265, + }, + Object { + "down": 2, + "timestamp": 1568173044768, + }, + Object { + "down": 2, + "timestamp": 1568173097271, + }, + Object { + "down": 1, + "timestamp": 1568173149774, + }, + Object { + "down": 2, + "timestamp": 1568173202277, + }, + ], + }, + "minInterval": 52503, + "monitor_id": "0010-down", + "state": Object { + "monitor": Object { + "name": "", + }, + "observer": Object { + "geo": Object { + "name": Array [ + "mpls", + ], + }, + }, + "summary": Object { + "down": 1, + "status": "down", + "up": 0, + }, + "summaryPings": Array [ + Object { + "@timestamp": "2019-09-11T03:40:34.371Z", + "agent": Object { + "ephemeral_id": "412a92a8-2142-4b1a-a7a2-1afd32e12f85", + "hostname": "avc-x1x", + "id": "04e1d082-65bc-4929-8d65-d0768a2621c4", + "type": "heartbeat", + "version": "8.0.0", + }, + "docId": "rZtoHm0B0I9WX_CznN_V", + "ecs": Object { + "version": "1.1.0", + }, + "error": Object { + "message": "400 Bad Request", + "type": "validate", + }, + "event": Object { + "dataset": "uptime", + }, + "host": Object { + "name": "avc-x1x", + }, + "http": Object { + "response": Object { + "body": Object { + "bytes": 3, + "content": "400", + "hash": "26d228663f13a88592a12d16cf9587caab0388b262d6d9f126ed62f9333aca94", + }, + "status_code": 400, + }, + "rtt": Object { + "content": Object { + "us": 41, + }, + "response_header": Object { + "us": 36777, + }, + "total": Object { + "us": 37821, + }, + "validate": Object { + "us": 36818, + }, + "write_request": Object { + "us": 53, + }, + }, + }, + "monitor": Object { + "check_group": "d76f07d1-d445-11e9-88e3-3e80641b9c71", + "duration": Object { + "us": 37926, + }, + "id": "0010-down", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "http", + }, + "observer": Object { + "geo": Object { + "location": "37.926868, -78.024902", + "name": "mpls", + }, + "hostname": "avc-x1x", + }, + "resolve": Object { + "ip": "127.0.0.1", + "rtt": Object { + "us": 56, + }, + }, + "summary": Object { + "down": 1, + "up": 0, + }, + "tcp": Object { + "rtt": Object { + "connect": Object { + "us": 890, + }, + }, + }, + "timestamp": "2019-09-11T03:40:34.371Z", + "url": Object { + "domain": "localhost", + "full": "http://localhost:5678/pattern?r=400x1", + "path": "/pattern", + "port": 5678, + "query": "r=400x1", + "scheme": "http", + }, + }, + ], + "timestamp": "2019-09-11T03:40:34.371Z", + "url": Object { + "domain": "localhost", + "full": "http://localhost:5678/pattern?r=400x1", + "path": "/pattern", + "port": 5678, + "query": "r=400x1", + "scheme": "http", + }, + }, + }, + Object { + "histogram": Object { + "points": Array [ + Object { + "down": 1, + "timestamp": 1568172624744, + }, + Object { + "down": 2, + "timestamp": 1568172677247, + }, + Object { + "down": 1, + "timestamp": 1568172729750, + }, + Object { + "down": 2, + "timestamp": 1568172782253, + }, + Object { + "down": 2, + "timestamp": 1568172834756, + }, + Object { + "down": 2, + "timestamp": 1568172887259, + }, + Object { + "down": 1, + "timestamp": 1568172939762, + }, + Object { + "down": 2, + "timestamp": 1568172992265, + }, + Object { + "down": 2, + "timestamp": 1568173044768, + }, + Object { + "down": 2, + "timestamp": 1568173097271, + }, + Object { + "down": 1, + "timestamp": 1568173149774, + }, + Object { + "down": 2, + "timestamp": 1568173202277, + }, + ], + }, + "minInterval": 52503, + "monitor_id": "0020-down", + "state": Object { + "monitor": Object { + "name": "", + }, + "observer": Object { + "geo": Object { + "name": Array [ + "mpls", + ], + }, + }, + "summary": Object { + "down": 1, + "status": "down", + "up": 0, + }, + "summaryPings": Array [ + Object { + "@timestamp": "2019-09-11T03:40:34.372Z", + "agent": Object { + "ephemeral_id": "412a92a8-2142-4b1a-a7a2-1afd32e12f85", + "hostname": "avc-x1x", + "id": "04e1d082-65bc-4929-8d65-d0768a2621c4", + "type": "heartbeat", + "version": "8.0.0", + }, + "docId": "X5toHm0B0I9WX_CznN-6", + "ecs": Object { + "version": "1.1.0", + }, + "error": Object { + "message": "400 Bad Request", + "type": "validate", + }, + "event": Object { + "dataset": "uptime", + }, + "host": Object { + "name": "avc-x1x", + }, + "http": Object { + "response": Object { + "body": Object { + "bytes": 3, + "content": "400", + "hash": "26d228663f13a88592a12d16cf9587caab0388b262d6d9f126ed62f9333aca94", + }, + "status_code": 400, + }, + "rtt": Object { + "content": Object { + "us": 54, + }, + "response_header": Object { + "us": 180, + }, + "total": Object { + "us": 555, + }, + "validate": Object { + "us": 234, + }, + "write_request": Object { + "us": 63, + }, + }, + }, + "monitor": Object { + "check_group": "d7712ecb-d445-11e9-88e3-3e80641b9c71", + "duration": Object { + "us": 14900, + }, + "id": "0020-down", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "http", + }, + "observer": Object { + "geo": Object { + "location": "37.926868, -78.024902", + "name": "mpls", + }, + "hostname": "avc-x1x", + }, + "resolve": Object { + "ip": "127.0.0.1", + "rtt": Object { + "us": 14294, + }, + }, + "summary": Object { + "down": 1, + "up": 0, + }, + "tcp": Object { + "rtt": Object { + "connect": Object { + "us": 105, + }, + }, + }, + "timestamp": "2019-09-11T03:40:34.372Z", + "url": Object { + "domain": "localhost", + "full": "http://localhost:5678/pattern?r=400x1", + "path": "/pattern", + "port": 5678, + "query": "r=400x1", + "scheme": "http", + }, + }, + ], + "timestamp": "2019-09-11T03:40:34.372Z", + "url": Object { + "domain": "localhost", + "full": "http://localhost:5678/pattern?r=400x1", + "path": "/pattern", + "port": 5678, + "query": "r=400x1", + "scheme": "http", + }, + }, + }, + ], + "totalSummaryCount": 2000, +} +`; diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index f59b79a6b7bfc..6f410add0fa4d 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -9,12 +9,15 @@ import { settingsObjectId, settingsObjectType, } from '../../../../../plugins/uptime/server/lib/saved_objects'; +import { registerMochaHooksForSnapshots } from '../../../../apm_api_integration/common/match_snapshot'; export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const server = getService('kibanaServer'); describe('uptime REST endpoints', () => { + registerMochaHooksForSnapshots(); + beforeEach('clear settings', async () => { try { await server.savedObjects.delete({ diff --git a/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts b/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts index d3c49bb49ff52..08a339ed59326 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts @@ -9,6 +9,7 @@ import { isRight } from 'fp-ts/lib/Either'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { MonitorSummariesResultType } from '../../../../../plugins/uptime/common/runtime_types'; import { API_URLS } from '../../../../../plugins/uptime/common/constants'; +import { expectSnapshot } from '../../../../apm_api_integration/common/match_snapshot'; interface ExpectedMonitorStatesPage { response: any; @@ -90,6 +91,16 @@ export default function ({ getService }: FtrProviderContext) { }); }); + it('will fetch monitor state data for the given down filters', async () => { + const statusFilter = 'down'; + const size = 2; + const { body } = await supertest.get( + `${API_URLS.MONITOR_LIST}?dateRangeStart=${from}&dateRangeEnd=${to}&statusFilter=${statusFilter}&pageSize=${size}` + ); + + expectSnapshot(body).toMatch(); + }); + it('can navigate forward and backward using pagination', async () => { const expectedResultsCount = 100; const size = 10; From 0a7f4629398048cbdd9a8995628203d5cc557637 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 17 Nov 2020 13:18:07 +0100 Subject: [PATCH 04/10] [Discover] Allow custom name for fields via index pattern field management (#70039) Co-authored-by: Matt Kime --- ...ugins-data-public.ifieldtype.customname.md | 11 +++ ...a-plugin-plugins-data-public.ifieldtype.md | 1 + ...ins-data-public.indexpattern.fieldattrs.md | 11 +++ ...ublic.indexpattern.getassavedobjectbody.md | 2 + ...-data-public.indexpattern.getfieldattrs.md | 15 +++ ...indexpattern.getoriginalsavedobjectbody.md | 1 + ...plugin-plugins-data-public.indexpattern.md | 4 +- ...ublic.indexpatternattributes.fieldattrs.md | 11 +++ ...gins-data-public.indexpatternattributes.md | 1 + ...-public.indexpatternfield._constructor_.md | 3 +- ...ata-public.indexpatternfield.customname.md | 13 +++ ...ta-public.indexpatternfield.displayname.md | 2 +- ...n-plugins-data-public.indexpatternfield.md | 3 +- ...ns-data-public.indexpatternfield.tojson.md | 2 + ...data-public.indexpatternspec.fieldattrs.md | 11 +++ ...in-plugins-data-public.indexpatternspec.md | 1 + ...ic.indexpatternsservice.fieldarraytomap.md | 2 +- ...lugins-data-public.indexpatternsservice.md | 2 +- ...ugins-data-server.ifieldtype.customname.md | 11 +++ ...a-plugin-plugins-data-server.ifieldtype.md | 1 + ...ins-data-server.indexpattern.fieldattrs.md | 11 +++ ...erver.indexpattern.getassavedobjectbody.md | 2 + ...-data-server.indexpattern.getfieldattrs.md | 15 +++ ...indexpattern.getoriginalsavedobjectbody.md | 1 + ...plugin-plugins-data-server.indexpattern.md | 4 +- ...erver.indexpatternattributes.fieldattrs.md | 11 +++ ...gins-data-server.indexpatternattributes.md | 1 + .../index_pattern_field.test.ts.snap | 2 + .../index_patterns/fields/field_list.ts | 8 +- .../fields/index_pattern_field.test.ts | 6 +- .../fields/index_pattern_field.ts | 25 ++++- .../common/index_patterns/fields/types.ts | 1 + .../__snapshots__/index_pattern.test.ts.snap | 57 +++++++++++ .../__snapshots__/index_patterns.test.ts.snap | 1 + .../index_patterns/index_pattern.ts | 23 ++++- .../index_patterns/index_patterns.ts | 44 ++++++--- .../data/common/index_patterns/types.ts | 10 +- src/plugins/data/public/public.api.md | 30 +++++- src/plugins/data/server/server.api.md | 20 +++- .../components/table_header/helpers.tsx | 3 +- .../table_header/table_header.test.tsx | 2 + .../table_header/table_header_column.tsx | 2 +- .../__snapshots__/field_name.test.tsx.snap | 4 +- .../components/field_name/field_name.test.tsx | 6 +- .../components/field_name/field_name.tsx | 13 +-- .../sidebar/discover_field.test.tsx | 46 ++++----- .../components/sidebar/discover_field.tsx | 17 +--- .../sidebar/discover_field_details.test.tsx | 69 ++++++-------- .../sidebar/discover_sidebar.test.tsx | 2 - .../components/sidebar/discover_sidebar.tsx | 6 +- .../sidebar/lib/field_filter.test.ts | 4 + .../components/sidebar/lib/field_filter.ts | 4 +- .../application/components/table/table.tsx | 8 +- .../components/table/table_row.tsx | 1 + .../application/doc_views/doc_views_types.ts | 1 + .../public/application/helpers/index.ts | 1 - .../helpers/shorten_dotted_string.ts | 26 ----- .../components/table/table.tsx | 25 ++++- .../indexed_fields_table.test.tsx | 5 +- .../indexed_fields_table.tsx | 6 +- .../__snapshots__/field_editor.test.tsx.snap | 94 ++++++++++++++++++- .../field_editor/field_editor.test.tsx | 56 +++++++++++ .../components/field_editor/field_editor.tsx | 38 +++++++- test/functional/apps/discover/_discover.js | 10 ++ test/functional/apps/visualize/_data_table.js | 23 +++++ .../fixtures/es_archiver/discover/data.json | 3 +- .../es_archiver/discover/mappings.json | 3 + .../fixtures/es_archiver/visualize/data.json | 3 +- .../es_archiver/visualize/mappings.json | 3 + test/functional/page_objects/discover_page.ts | 4 +- .../page_objects/visualize_chart_page.ts | 7 ++ .../discover/default/mappings.json | 3 + 72 files changed, 690 insertions(+), 188 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.customname.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldattrs.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.fieldattrs.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.customname.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fieldattrs.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.customname.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldattrs.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.fieldattrs.md delete mode 100644 src/plugins/discover/public/application/helpers/shorten_dotted_string.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.customname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.customname.md new file mode 100644 index 0000000000000..b30201f9e3991 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.customname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [customName](./kibana-plugin-plugins-data-public.ifieldtype.customname.md) + +## IFieldType.customName property + +Signature: + +```typescript +customName?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md index 3ff2afafcc514..6f3876ff82f04 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md @@ -16,6 +16,7 @@ export interface IFieldType | --- | --- | --- | | [aggregatable](./kibana-plugin-plugins-data-public.ifieldtype.aggregatable.md) | boolean | | | [count](./kibana-plugin-plugins-data-public.ifieldtype.count.md) | number | | +| [customName](./kibana-plugin-plugins-data-public.ifieldtype.customname.md) | string | | | [displayName](./kibana-plugin-plugins-data-public.ifieldtype.displayname.md) | string | | | [esTypes](./kibana-plugin-plugins-data-public.ifieldtype.estypes.md) | string[] | | | [filterable](./kibana-plugin-plugins-data-public.ifieldtype.filterable.md) | boolean | | 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 new file mode 100644 index 0000000000000..c2e0b9bb855f4 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldattrs.md @@ -0,0 +1,11 @@ + + +[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.getassavedobjectbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md index 2c5f30e4889ea..a370341000960 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 @@ -10,6 +10,7 @@ Returns index pattern as saved object body for saving ```typescript getAsSavedObjectBody(): { + fieldAttrs: string | undefined; title: string; timeFieldName: string | undefined; intervalName: string | undefined; @@ -23,6 +24,7 @@ getAsSavedObjectBody(): { Returns: `{ + fieldAttrs: string | undefined; title: string; timeFieldName: string | undefined; intervalName: string | undefined; diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md new file mode 100644 index 0000000000000..f81edf4b94b42 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [getFieldAttrs](./kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md) + +## IndexPattern.getFieldAttrs property + +Signature: + +```typescript +getFieldAttrs: () => { + [x: string]: { + customName: string; + }; + }; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md index 349da63c13ca7..0c89a6a3d20ba 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md @@ -10,6 +10,7 @@ Get last saved saved object fields ```typescript getOriginalSavedObjectBody: () => { + fieldAttrs?: string | undefined; title?: string | undefined; timeFieldName?: string | undefined; intervalName?: string | undefined; 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 7e3192481dfff..1228bf7adc2ef 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 @@ -21,12 +21,14 @@ export declare class IndexPattern implements IIndexPattern | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [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> | | | [formatField](./kibana-plugin-plugins-data-public.indexpattern.formatfield.md) | | FormatFieldFn | | | [formatHit](./kibana-plugin-plugins-data-public.indexpattern.formathit.md) | | {
(hit: Record<string, any>, type?: string): any;
formatField: FormatFieldFn;
} | | -| [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md) | | () => {
title?: string | undefined;
timeFieldName?: string | undefined;
intervalName?: string | undefined;
fields?: string | undefined;
sourceFilters?: string | undefined;
fieldFormatMap?: string | undefined;
typeMeta?: string | undefined;
type?: string | undefined;
} | Get last saved saved object fields | +| [getFieldAttrs](./kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md) | | () => {
[x: string]: {
customName: string;
};
} | | +| [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md) | | () => {
fieldAttrs?: string | undefined;
title?: string | undefined;
timeFieldName?: string | undefined;
intervalName?: string | undefined;
fields?: string | undefined;
sourceFilters?: string | undefined;
fieldFormatMap?: string | undefined;
typeMeta?: string | undefined;
type?: string | undefined;
} | Get last saved saved object fields | | [id](./kibana-plugin-plugins-data-public.indexpattern.id.md) | | string | | | [intervalName](./kibana-plugin-plugins-data-public.indexpattern.intervalname.md) | | string | undefined | | | [metaFields](./kibana-plugin-plugins-data-public.indexpattern.metafields.md) | | string[] | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.fieldattrs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.fieldattrs.md new file mode 100644 index 0000000000000..6af981eb6996c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.fieldattrs.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) > [fieldAttrs](./kibana-plugin-plugins-data-public.indexpatternattributes.fieldattrs.md) + +## IndexPatternAttributes.fieldAttrs property + +Signature: + +```typescript +fieldAttrs?: string; +``` 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 77a8ebb0b2d3f..c5ea38278e820 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 @@ -14,6 +14,7 @@ export interface IndexPatternAttributes | Property | Type | Description | | --- | --- | --- | +| [fieldAttrs](./kibana-plugin-plugins-data-public.indexpatternattributes.fieldattrs.md) | string | | | [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 | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md index 5d467a7a9cbce..e0abf8aeeaee6 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `IndexPatternField` class Signature: ```typescript -constructor(spec: FieldSpec, displayName: string); +constructor(spec: FieldSpec); ``` ## Parameters @@ -17,5 +17,4 @@ constructor(spec: FieldSpec, displayName: string); | Parameter | Type | Description | | --- | --- | --- | | spec | FieldSpec | | -| displayName | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.customname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.customname.md new file mode 100644 index 0000000000000..ef8f9f1d31e4f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.customname.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) > [customName](./kibana-plugin-plugins-data-public.indexpatternfield.customname.md) + +## IndexPatternField.customName property + +Signature: + +```typescript +get customName(): string | undefined; + +set customName(label: string | undefined); +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.displayname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.displayname.md index c0ce2fff419bf..913d63c93e3c0 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.displayname.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.displayname.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly displayName: string; +get displayName(): string; ``` 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 4f49a9a8fc3ab..ef99b4353a70b 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 @@ -14,7 +14,7 @@ export declare class IndexPatternField implements IFieldType | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(spec, displayName)](./kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md) | | Constructs a new instance of the IndexPatternField class | +| [(constructor)(spec)](./kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md) | | Constructs a new instance of the IndexPatternField class | ## Properties @@ -23,6 +23,7 @@ export declare class IndexPatternField implements IFieldType | [aggregatable](./kibana-plugin-plugins-data-public.indexpatternfield.aggregatable.md) | | boolean | | | [conflictDescriptions](./kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md) | | Record<string, string[]> | undefined | Description of field type conflicts across different indices in the same index pattern | | [count](./kibana-plugin-plugins-data-public.indexpatternfield.count.md) | | number | Count is used for field popularity | +| [customName](./kibana-plugin-plugins-data-public.indexpatternfield.customname.md) | | string | undefined | | | [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 | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md index a6a3a5a093c8e..c7237701ae49d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md @@ -20,6 +20,7 @@ toJSON(): { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; + customName: string | undefined; }; ``` Returns: @@ -37,5 +38,6 @@ toJSON(): { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; + customName: string | undefined; }` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fieldattrs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fieldattrs.md new file mode 100644 index 0000000000000..e558c3ab19189 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fieldattrs.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) > [fieldAttrs](./kibana-plugin-plugins-data-public.indexpatternspec.fieldattrs.md) + +## IndexPatternSpec.fieldAttrs property + +Signature: + +```typescript +fieldAttrs?: FieldAttrs; +``` 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 f3b692209ca67..06917fcac1b4d 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 @@ -14,6 +14,7 @@ export interface IndexPatternSpec | Property | Type | Description | | --- | --- | --- | +| [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 | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md index ed365fe03f980..2a09d5b3adb1d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md @@ -9,5 +9,5 @@ Converts field array to map Signature: ```typescript -fieldArrayToMap: (fields: FieldSpec[]) => Record; +fieldArrayToMap: (fields: FieldSpec[], fieldAttrs?: FieldAttrs | undefined) => Record; ``` 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 57bb98de09ebd..48019fe410b97 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 @@ -22,7 +22,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[]) => Record<string, FieldSpec> | Converts field array to map | +| [fieldArrayToMap](./kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md) | | (fields: FieldSpec[], fieldAttrs?: FieldAttrs | undefined) => Record<string, FieldSpec> | Converts field array to map | | [get](./kibana-plugin-plugins-data-public.indexpatternsservice.get.md) | | (id: string) => Promise<IndexPattern> | Get an index pattern by id. Cache optimized | | [getCache](./kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md) | | () => Promise<SavedObject<IndexPatternSavedObjectAttrs>[] | null | undefined> | | | [getDefault](./kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md) | | () => Promise<IndexPattern | null> | Get default index pattern | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.customname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.customname.md new file mode 100644 index 0000000000000..f5fbc084237f2 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.customname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [customName](./kibana-plugin-plugins-data-server.ifieldtype.customname.md) + +## IFieldType.customName property + +Signature: + +```typescript +customName?: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md index d106f3a35a91c..638700b1d24f8 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md @@ -16,6 +16,7 @@ export interface IFieldType | --- | --- | --- | | [aggregatable](./kibana-plugin-plugins-data-server.ifieldtype.aggregatable.md) | boolean | | | [count](./kibana-plugin-plugins-data-server.ifieldtype.count.md) | number | | +| [customName](./kibana-plugin-plugins-data-server.ifieldtype.customname.md) | string | | | [displayName](./kibana-plugin-plugins-data-server.ifieldtype.displayname.md) | string | | | [esTypes](./kibana-plugin-plugins-data-server.ifieldtype.estypes.md) | string[] | | | [filterable](./kibana-plugin-plugins-data-server.ifieldtype.filterable.md) | boolean | | 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 new file mode 100644 index 0000000000000..c8bad55dee2e4 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldattrs.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [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.getassavedobjectbody.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md index f1bdb2f729414..274a475872b0b 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 @@ -10,6 +10,7 @@ Returns index pattern as saved object body for saving ```typescript getAsSavedObjectBody(): { + fieldAttrs: string | undefined; title: string; timeFieldName: string | undefined; intervalName: string | undefined; @@ -23,6 +24,7 @@ getAsSavedObjectBody(): { Returns: `{ + fieldAttrs: string | undefined; title: string; timeFieldName: string | undefined; intervalName: string | undefined; diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md new file mode 100644 index 0000000000000..80dd329232ed8 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getFieldAttrs](./kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md) + +## IndexPattern.getFieldAttrs property + +Signature: + +```typescript +getFieldAttrs: () => { + [x: string]: { + customName: string; + }; + }; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md index 324f9d0152ab5..9923c82f389ad 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md @@ -10,6 +10,7 @@ Get last saved saved object fields ```typescript getOriginalSavedObjectBody: () => { + fieldAttrs?: string | undefined; title?: string | undefined; timeFieldName?: string | undefined; intervalName?: string | undefined; 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 2e15c8d3867ec..3d2b021b29515 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 @@ -21,12 +21,14 @@ export declare class IndexPattern implements IIndexPattern | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [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> | | | [formatField](./kibana-plugin-plugins-data-server.indexpattern.formatfield.md) | | FormatFieldFn | | | [formatHit](./kibana-plugin-plugins-data-server.indexpattern.formathit.md) | | {
(hit: Record<string, any>, type?: string): any;
formatField: FormatFieldFn;
} | | -| [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md) | | () => {
title?: string | undefined;
timeFieldName?: string | undefined;
intervalName?: string | undefined;
fields?: string | undefined;
sourceFilters?: string | undefined;
fieldFormatMap?: string | undefined;
typeMeta?: string | undefined;
type?: string | undefined;
} | Get last saved saved object fields | +| [getFieldAttrs](./kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md) | | () => {
[x: string]: {
customName: string;
};
} | | +| [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md) | | () => {
fieldAttrs?: string | undefined;
title?: string | undefined;
timeFieldName?: string | undefined;
intervalName?: string | undefined;
fields?: string | undefined;
sourceFilters?: string | undefined;
fieldFormatMap?: string | undefined;
typeMeta?: string | undefined;
type?: string | undefined;
} | Get last saved saved object fields | | [id](./kibana-plugin-plugins-data-server.indexpattern.id.md) | | string | | | [intervalName](./kibana-plugin-plugins-data-server.indexpattern.intervalname.md) | | string | undefined | | | [metaFields](./kibana-plugin-plugins-data-server.indexpattern.metafields.md) | | string[] | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.fieldattrs.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.fieldattrs.md new file mode 100644 index 0000000000000..fded3ebac8b2c --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.fieldattrs.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) > [fieldAttrs](./kibana-plugin-plugins-data-server.indexpatternattributes.fieldattrs.md) + +## IndexPatternAttributes.fieldAttrs property + +Signature: + +```typescript +fieldAttrs?: string; +``` 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 40b029da00469..6559b4d7110be 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 @@ -14,6 +14,7 @@ export interface IndexPatternAttributes | Property | Type | Description | | --- | --- | --- | +| [fieldAttrs](./kibana-plugin-plugins-data-server.indexpatternattributes.fieldattrs.md) | string | | | [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 | | 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 4279dd320ad62..afaa2d00d8cfd 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 @@ -47,6 +47,7 @@ Object { ], }, "count": 1, + "customName": undefined, "esTypes": Array [ "text", ], @@ -62,6 +63,7 @@ Object { "script": "script", "scripted": true, "searchable": true, + "shortDotsEnable": undefined, "subType": Object { "multi": Object { "parent": "parent", diff --git a/src/plugins/data/common/index_patterns/fields/field_list.ts b/src/plugins/data/common/index_patterns/fields/field_list.ts index c0eb55a15fead..7fe2a17124b78 100644 --- a/src/plugins/data/common/index_patterns/fields/field_list.ts +++ b/src/plugins/data/common/index_patterns/fields/field_list.ts @@ -22,7 +22,6 @@ import { IFieldType } from './types'; import { IndexPatternField } from './index_pattern_field'; import { FieldSpec, IndexPatternFieldMap } from '../types'; import { IndexPattern } from '../index_patterns'; -import { shortenDottedString } from '../../utils'; type FieldMap = Map; @@ -58,8 +57,7 @@ export const fieldList = ( this.groups.get(field.type)!.set(field.name, field); }; private removeByGroup = (field: IFieldType) => this.groups.get(field.type)!.delete(field.name); - private calcDisplayName = (name: string) => - shortDotsEnable ? shortenDottedString(name) : name; + constructor() { super(); specs.map((field) => this.add(field)); @@ -71,7 +69,7 @@ export const fieldList = ( ...(this.groups.get(type) || new Map()).values(), ]; public readonly add = (field: FieldSpec) => { - const newField = new IndexPatternField(field, this.calcDisplayName(field.name)); + const newField = new IndexPatternField({ ...field, shortDotsEnable }); this.push(newField); this.setByName(newField); this.setByGroup(newField); @@ -86,7 +84,7 @@ export const fieldList = ( }; public readonly update = (field: FieldSpec) => { - const newField = new IndexPatternField(field, this.calcDisplayName(field.name)); + const newField = new IndexPatternField(field); const index = this.findIndex((f) => f.name === newField.name); this.splice(index, 1, newField); this.setByName(newField); 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 be7836de31246..81c7d6b9b237b 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 @@ -28,7 +28,7 @@ describe('Field', function () { } function getField(values = {}) { - return new IndexPatternField({ ...fieldValues, ...values }, 'displayName'); + return new IndexPatternField({ ...fieldValues, ...values }); } const fieldValues = { @@ -150,12 +150,12 @@ describe('Field', function () { }); it('exports the property to JSON', () => { - const field = new IndexPatternField(fieldValues, 'displayName'); + const field = new IndexPatternField(fieldValues); expect(flatten(field)).toMatchSnapshot(); }); it('spec snapshot', () => { - const field = new IndexPatternField(fieldValues, 'displayName'); + const field = new IndexPatternField(fieldValues); const getFormatterForField = () => ({ toJSON: () => ({ 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 4a22508f7fef3..850c5a312fda1 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 @@ -21,16 +21,15 @@ import { KbnFieldType, getKbnFieldType } from '../../kbn_field_types'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { IFieldType } from './types'; import { FieldSpec, IndexPattern } from '../..'; +import { shortenDottedString } from '../../utils'; export class IndexPatternField implements IFieldType { readonly spec: FieldSpec; // not writable or serialized - readonly displayName: string; private readonly kbnFieldType: KbnFieldType; - constructor(spec: FieldSpec, displayName: string) { + constructor(spec: FieldSpec) { this.spec = { ...spec, type: spec.name === '_source' ? '_source' : spec.type }; - this.displayName = displayName; this.kbnFieldType = getKbnFieldType(spec.type); } @@ -69,6 +68,14 @@ export class IndexPatternField implements IFieldType { this.spec.lang = lang; } + public get customName() { + return this.spec.customName; + } + + public set customName(label) { + this.spec.customName = label; + } + /** * Description of field type conflicts across different indices in the same index pattern */ @@ -85,6 +92,14 @@ export class IndexPatternField implements IFieldType { return this.spec.name; } + public get displayName(): string { + return this.spec.customName + ? this.spec.customName + : this.spec.shortDotsEnable + ? shortenDottedString(this.spec.name) + : this.spec.name; + } + public get type() { return this.spec.type; } @@ -140,7 +155,6 @@ export class IndexPatternField implements IFieldType { script: this.script, lang: this.lang, conflictDescriptions: this.conflictDescriptions, - name: this.name, type: this.type, esTypes: this.esTypes, @@ -149,6 +163,7 @@ export class IndexPatternField implements IFieldType { aggregatable: this.aggregatable, readFromDocValues: this.readFromDocValues, subType: this.subType, + customName: this.customName, }; } @@ -171,6 +186,8 @@ export class IndexPatternField implements IFieldType { readFromDocValues: this.readFromDocValues, subType: this.subType, format: getFormatterForField ? getFormatterForField(this).toJSON() : undefined, + customName: this.customName, + shortDotsEnable: this.spec.shortDotsEnable, }; } } diff --git a/src/plugins/data/common/index_patterns/fields/types.ts b/src/plugins/data/common/index_patterns/fields/types.ts index 5814760601a67..86c22b0116ead 100644 --- a/src/plugins/data/common/index_patterns/fields/types.ts +++ b/src/plugins/data/common/index_patterns/fields/types.ts @@ -37,6 +37,7 @@ export interface IFieldType { scripted?: boolean; subType?: IFieldSubType; displayName?: string; + customName?: string; format?: any; toSpec?: (options?: { getFormatterForField?: IndexPattern['getFormatterForField'] }) => FieldSpec; } 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 dc4da2456b47b..2741322acec0f 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap @@ -2,12 +2,14 @@ exports[`IndexPattern toSpec should match snapshot 1`] = ` Object { + "fieldAttrs": Object {}, "fieldFormats": Object {}, "fields": Object { "@tags": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "keyword", ], @@ -23,6 +25,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "string", }, @@ -30,6 +33,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 30, + "customName": undefined, "esTypes": Array [ "date", ], @@ -45,6 +49,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "date", }, @@ -52,6 +57,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "_id", ], @@ -67,6 +73,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "string", }, @@ -74,6 +81,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "_source", ], @@ -89,6 +97,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "_source", }, @@ -96,6 +105,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "_type", ], @@ -111,6 +121,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "string", }, @@ -118,6 +129,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "geo_shape", ], @@ -133,6 +145,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "geo_shape", }, @@ -140,6 +153,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 10, + "customName": undefined, "esTypes": Array [ "long", ], @@ -155,6 +169,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "number", }, @@ -162,6 +177,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "conflict", ], @@ -177,6 +193,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "conflict", }, @@ -184,6 +201,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "text", ], @@ -199,6 +217,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "string", }, @@ -206,6 +225,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "keyword", ], @@ -221,6 +241,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": Object { "multi": Object { "parent": "extension", @@ -232,6 +253,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "geo_point", ], @@ -247,6 +269,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "geo_point", }, @@ -254,6 +277,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "keyword", ], @@ -269,6 +293,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "string", }, @@ -276,6 +301,7 @@ Object { "aggregatable": false, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "murmur3", ], @@ -291,6 +317,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "murmur3", }, @@ -298,6 +325,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "ip", ], @@ -313,6 +341,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "ip", }, @@ -320,6 +349,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "text", ], @@ -335,6 +365,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "string", }, @@ -342,6 +373,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "keyword", ], @@ -357,6 +389,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": Object { "multi": Object { "parent": "machine.os", @@ -368,6 +401,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "text", ], @@ -383,6 +417,7 @@ Object { "script": undefined, "scripted": false, "searchable": false, + "shortDotsEnable": false, "subType": undefined, "type": "string", }, @@ -390,6 +425,7 @@ Object { "aggregatable": false, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "text", ], @@ -405,6 +441,7 @@ Object { "script": undefined, "scripted": false, "searchable": false, + "shortDotsEnable": false, "subType": undefined, "type": "string", }, @@ -412,6 +449,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "integer", ], @@ -427,6 +465,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "number", }, @@ -434,6 +473,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "geo_point", ], @@ -449,6 +489,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "geo_point", }, @@ -456,6 +497,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "attachment", ], @@ -471,6 +513,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "attachment", }, @@ -478,6 +521,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "date", ], @@ -493,6 +537,7 @@ Object { "script": "1234", "scripted": true, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "date", }, @@ -500,6 +545,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "murmur3", ], @@ -515,6 +561,7 @@ Object { "script": "1234", "scripted": true, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "murmur3", }, @@ -522,6 +569,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "long", ], @@ -537,6 +585,7 @@ Object { "script": "1234", "scripted": true, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "number", }, @@ -544,6 +593,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "text", ], @@ -559,6 +609,7 @@ Object { "script": "'i am a string'", "scripted": true, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "string", }, @@ -566,6 +617,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 20, + "customName": undefined, "esTypes": Array [ "boolean", ], @@ -581,6 +633,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "boolean", }, @@ -588,6 +641,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 30, + "customName": undefined, "esTypes": Array [ "date", ], @@ -603,6 +657,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "date", }, @@ -610,6 +665,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, + "customName": undefined, "esTypes": Array [ "date", ], @@ -625,6 +681,7 @@ Object { "script": undefined, "scripted": false, "searchable": true, + "shortDotsEnable": false, "subType": undefined, "type": "date", }, 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 a3d19f311b765..c020e7595c565 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 @@ -2,6 +2,7 @@ exports[`IndexPatterns savedObjectToSpec 1`] = ` Object { + "fieldAttrs": Object {}, "fieldFormats": Object { "field": Object {}, }, 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 4508d7b1d9082..c3a0c98745e21 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -18,6 +18,7 @@ */ import _, { each, reject } from 'lodash'; +import { FieldAttrs } from '../..'; import { DuplicateField } from '../../../../kibana_utils/common'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../../../common'; @@ -36,6 +37,7 @@ interface IndexPatternDeps { } interface SavedObjectBody { + fieldAttrs?: string; title?: string; timeFieldName?: string; intervalName?: string; @@ -70,6 +72,8 @@ export class IndexPattern implements IIndexPattern { private originalSavedObjectBody: SavedObjectBody = {}; private shortDotsEnable: boolean = false; private fieldFormats: FieldFormatsStartCommon; + // make private once manual field refresh is removed + public fieldAttrs: FieldAttrs; constructor({ spec = {}, @@ -101,10 +105,10 @@ export class IndexPattern implements IIndexPattern { this.title = spec.title || ''; this.timeFieldName = spec.timeFieldName; this.sourceFilters = spec.sourceFilters; - this.fields.replaceAll(Object.values(spec.fields || {})); this.type = spec.type; this.typeMeta = spec.typeMeta; + this.fieldAttrs = spec.fieldAttrs || {}; } setFieldFormat = (fieldName: string, format: SerializedFieldFormat) => { @@ -127,6 +131,20 @@ export class IndexPattern implements IIndexPattern { this.originalSavedObjectBody = this.getAsSavedObjectBody(); }; + getFieldAttrs = () => { + const newFieldAttrs = { ...this.fieldAttrs }; + + this.fields.forEach((field) => { + if (field.customName) { + newFieldAttrs[field.name] = { customName: field.customName }; + } else { + delete newFieldAttrs[field.name]; + } + }); + + return newFieldAttrs; + }; + getComputedFields() { const scriptFields: any = {}; if (!this.fields) { @@ -180,6 +198,7 @@ export class IndexPattern implements IIndexPattern { typeMeta: this.typeMeta, type: this.type, fieldFormats: this.fieldFormatMap, + fieldAttrs: this.fieldAttrs, }; } @@ -271,8 +290,10 @@ export class IndexPattern implements IIndexPattern { const fieldFormatMap = _.isEmpty(this.fieldFormatMap) ? undefined : JSON.stringify(this.fieldFormatMap); + const fieldAttrs = this.getFieldAttrs(); return { + fieldAttrs: fieldAttrs ? JSON.stringify(fieldAttrs) : undefined, title: this.title, timeFieldName: this.timeFieldName, intervalName: this.intervalName, 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 19c6e9c7b8a7a..4f91079c1e139 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 @@ -35,6 +35,7 @@ import { GetFieldsOptions, IndexPatternSpec, IndexPatternAttributes, + FieldAttrs, FieldSpec, IndexPatternFieldMap, } from '../types'; @@ -249,7 +250,11 @@ export class IndexPatternsService { try { const fields = await this.getFieldsForIndexPattern(indexPattern); const scripted = indexPattern.getScriptedFields().map((field) => field.spec); - indexPattern.fields.replaceAll([...fields, ...scripted]); + const fieldAttrs = indexPattern.getFieldAttrs(); + const fieldsWithSavedAttrs = Object.values( + this.fieldArrayToMap([...fields, ...scripted], fieldAttrs) + ); + indexPattern.fields.replaceAll(fieldsWithSavedAttrs); } catch (err) { if (err instanceof IndexPatternMissingIndices) { this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); @@ -275,12 +280,13 @@ export class IndexPatternsService { fields: IndexPatternFieldMap, id: string, title: string, - options: GetFieldsOptions + options: GetFieldsOptions, + fieldAttrs: FieldAttrs = {} ) => { const scriptdFields = Object.values(fields).filter((field) => field.scripted); try { - const newFields = await this.getFieldsForWildcard(options); - return this.fieldArrayToMap([...newFields, ...scriptdFields]); + const newFields = (await this.getFieldsForWildcard(options)) as FieldSpec[]; + return this.fieldArrayToMap([...newFields, ...scriptdFields], fieldAttrs); } catch (err) { if (err instanceof IndexPatternMissingIndices) { this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); @@ -301,9 +307,9 @@ export class IndexPatternsService { * Converts field array to map * @param fields */ - fieldArrayToMap = (fields: FieldSpec[]) => + fieldArrayToMap = (fields: FieldSpec[], fieldAttrs?: FieldAttrs) => fields.reduce((collector, field) => { - collector[field.name] = field; + collector[field.name] = { ...field, customName: fieldAttrs?.[field.name]?.customName }; return collector; }, {}); @@ -325,6 +331,7 @@ export class IndexPatternsService { fieldFormatMap, typeMeta, type, + fieldAttrs, }, } = savedObject; @@ -332,6 +339,7 @@ export class IndexPatternsService { const parsedTypeMeta = typeMeta ? JSON.parse(typeMeta) : undefined; const parsedFieldFormatMap = fieldFormatMap ? JSON.parse(fieldFormatMap) : {}; const parsedFields: FieldSpec[] = fields ? JSON.parse(fields) : []; + const parsedFieldAttrs: FieldAttrs = fieldAttrs ? JSON.parse(fieldAttrs) : {}; return { id, @@ -340,10 +348,11 @@ export class IndexPatternsService { intervalName, timeFieldName, sourceFilters: parsedSourceFilters, - fields: this.fieldArrayToMap(parsedFields), + fields: this.fieldArrayToMap(parsedFields, parsedFieldAttrs), typeMeta: parsedTypeMeta, type, fieldFormats: parsedFieldFormatMap, + fieldAttrs: parsedFieldAttrs, }; }; @@ -369,17 +378,26 @@ export class IndexPatternsService { const spec = this.savedObjectToSpec(savedObject); const { title, type, typeMeta } = spec; + spec.fieldAttrs = savedObject.attributes.fieldAttrs + ? JSON.parse(savedObject.attributes.fieldAttrs) + : {}; const isFieldRefreshRequired = this.isFieldRefreshRequired(spec.fields); let isSaveRequired = isFieldRefreshRequired; try { spec.fields = isFieldRefreshRequired - ? await this.refreshFieldSpecMap(spec.fields || {}, id, spec.title as string, { - pattern: title as string, - metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), - type, - rollupIndex: typeMeta?.params?.rollupIndex, - }) + ? await this.refreshFieldSpecMap( + spec.fields || {}, + id, + spec.title as string, + { + pattern: title as string, + metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), + type, + rollupIndex: typeMeta?.params?.rollupIndex, + }, + spec.fieldAttrs + ) : spec.fields; } catch (err) { isSaveRequired = false; diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index b381cc0963333..22c400562f6d4 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -48,6 +48,11 @@ export interface IndexPatternAttributes { intervalName?: string; sourceFilters?: string; fieldFormatMap?: string; + fieldAttrs?: string; +} + +export interface FieldAttrs { + [key: string]: { customName: string }; } export type OnNotification = (toastInputFields: ToastInputFields) => void; @@ -155,7 +160,6 @@ export interface FieldSpec { lang?: string; conflictDescriptions?: Record; format?: SerializedFieldFormat; - name: string; type: string; esTypes?: string[]; @@ -165,6 +169,9 @@ export interface FieldSpec { readFromDocValues?: boolean; subType?: IFieldSubType; indexed?: boolean; + customName?: string; + // not persisted + shortDotsEnable?: boolean; } export type IndexPatternFieldMap = Record; @@ -180,6 +187,7 @@ export interface IndexPatternSpec { typeMeta?: TypeMeta; type?: string; fieldFormats?: Record; + fieldAttrs?: FieldAttrs; } export interface SourceFilter { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 78b974758f8c0..0768658e40299 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -975,6 +975,8 @@ export interface IFieldType { // (undocumented) count?: number; // (undocumented) + customName?: string; + // (undocumented) displayName?: string; // (undocumented) esTypes?: string[]; @@ -1096,6 +1098,10 @@ export class IndexPattern implements IIndexPattern { addScriptedField(name: string, script: string, fieldType?: string): Promise; // (undocumented) 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) @@ -1121,6 +1127,7 @@ export class IndexPattern implements IIndexPattern { time_zone?: string | undefined; }>> | undefined; getAsSavedObjectBody(): { + fieldAttrs: string | undefined; title: string; timeFieldName: string | undefined; intervalName: string | undefined; @@ -1140,12 +1147,19 @@ export class IndexPattern implements IIndexPattern { }[]; }; // (undocumented) + getFieldAttrs: () => { + [x: string]: { + customName: string; + }; + }; + // (undocumented) getFieldByName(name: string): IndexPatternField | undefined; getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; getFormatterForFieldNoDefault(fieldname: string): FieldFormat | undefined; // (undocumented) getNonScriptedFields(): IndexPatternField[]; getOriginalSavedObjectBody: () => { + fieldAttrs?: string | undefined; title?: string | undefined; timeFieldName?: string | undefined; intervalName?: string | undefined; @@ -1210,6 +1224,8 @@ export type IndexPatternAggRestrictions = Record | undefined; @@ -1240,7 +1256,10 @@ export class IndexPatternField implements IFieldType { get count(): number; set count(count: number); // (undocumented) - readonly displayName: string; + get customName(): string | undefined; + set customName(label: string | undefined); + // (undocumented) + get displayName(): string; // (undocumented) get esTypes(): string[] | undefined; // (undocumented) @@ -1277,6 +1296,7 @@ export class IndexPatternField implements IFieldType { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; + customName: string | undefined; }; // (undocumented) toSpec({ getFormatterForField, }?: { @@ -1324,6 +1344,8 @@ export type IndexPatternSelectProps = Required, 'isLo // // @public (undocumented) export interface IndexPatternSpec { + // (undocumented) + fieldAttrs?: FieldAttrs; // (undocumented) fieldFormats?: Record; // (undocumented) @@ -1361,7 +1383,7 @@ export class IndexPatternsService { // // (undocumented) ensureDefaultIndexPattern: EnsureDefaultIndexPattern; - fieldArrayToMap: (fields: FieldSpec[]) => Record; + fieldArrayToMap: (fields: FieldSpec[], fieldAttrs?: FieldAttrs | undefined) => Record; get: (id: string) => Promise; // Warning: (ae-forgotten-export) The symbol "IndexPatternSavedObjectAttrs" needs to be exported by the entry point index.d.ts // @@ -2325,7 +2347,7 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/meta_filter.ts:54:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:62: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:64:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:113:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:67:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" 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 bb7a8f58c926c..b2db4f5c74729 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -507,6 +507,8 @@ export interface IFieldType { // (undocumented) count?: number; // (undocumented) + customName?: string; + // (undocumented) displayName?: string; // (undocumented) esTypes?: string[]; @@ -557,6 +559,10 @@ export class IndexPattern implements IIndexPattern { addScriptedField(name: string, script: string, fieldType?: string): Promise; // (undocumented) 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 @@ -584,6 +590,7 @@ export class IndexPattern implements IIndexPattern { time_zone?: string | undefined; }>> | undefined; getAsSavedObjectBody(): { + fieldAttrs: string | undefined; title: string; timeFieldName: string | undefined; intervalName: string | undefined; @@ -603,6 +610,12 @@ export class IndexPattern implements IIndexPattern { }[]; }; // (undocumented) + getFieldAttrs: () => { + [x: string]: { + customName: string; + }; + }; + // (undocumented) getFieldByName(name: string): IndexPatternField | undefined; getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; getFormatterForFieldNoDefault(fieldname: string): FieldFormat | undefined; @@ -611,6 +624,7 @@ export class IndexPattern implements IIndexPattern { // (undocumented) getNonScriptedFields(): IndexPatternField[]; getOriginalSavedObjectBody: () => { + fieldAttrs?: string | undefined; title?: string | undefined; timeFieldName?: string | undefined; intervalName?: string | undefined; @@ -669,6 +683,8 @@ export class IndexPattern implements IIndexPattern { // // @public (undocumented) export interface IndexPatternAttributes { + // (undocumented) + fieldAttrs?: string; // (undocumented) fieldFormatMap?: string; // (undocumented) @@ -1195,8 +1211,8 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // // src/plugins/data/common/es_query/filters/meta_filter.ts:53:3 - (ae-forgotten-export) The symbol "FilterState" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/meta_filter.ts:54:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:56: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:62: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:58: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:64:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx index bd48b1e083871..b456fa0773b85 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx @@ -17,7 +17,6 @@ * under the License. */ import { IndexPattern } from '../../../../../kibana_services'; -import { shortenDottedString } from '../../../../helpers'; export type SortOrder = [string, string]; export interface ColumnProps { @@ -67,7 +66,7 @@ export function getDisplayedColumns( const field = indexPattern.getFieldByName(column); return { name: column, - displayName: isShortDots ? shortenDottedString(column) : column, + displayName: field ? field.displayName : column, isSortable: field && field.sortable ? true : false, isRemoveable: column !== '_source' || columns.length > 1, colLeftIdx: idx - 1 < 0 ? -1 : idx - 1, diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx index 3d5698e2e0d96..7636939194ce1 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx @@ -35,6 +35,7 @@ function getMockIndexPattern() { if (name === 'test1') { return { name, + displayName: name, type: 'string', aggregatable: false, searchable: true, @@ -43,6 +44,7 @@ function getMockIndexPattern() { } else { return { name, + displayName: name, type: 'string', aggregatable: false, searchable: true, diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx index ac986fcaf0cbc..08a2d07d0b8e0 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx @@ -24,7 +24,7 @@ import { SortOrder } from './helpers'; interface Props { colLeftIdx: number; // idx of the column to the left, -1 if moving is not possible colRightIdx: number; // idx of the column to the right, -1 if moving is not possible - displayName: string; + displayName?: string; isRemoveable: boolean; isSortable: boolean; name: string; diff --git a/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap b/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap index d00a956b7c73d..2fa96f9372380 100644 --- a/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap +++ b/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FieldName renders a geo field, useShortDots is set to true 1`] = ` +exports[`FieldName renders a geo field 1`] = `
@@ -24,7 +24,7 @@ exports[`FieldName renders a geo field, useShortDots is set to true 1`] = ` class="euiToolTipAnchor eui-textTruncate" > - t.t.test + test.test.test
diff --git a/src/plugins/discover/public/application/components/field_name/field_name.test.tsx b/src/plugins/discover/public/application/components/field_name/field_name.test.tsx index e6cf8a57686f1..0deddce1c40a8 100644 --- a/src/plugins/discover/public/application/components/field_name/field_name.test.tsx +++ b/src/plugins/discover/public/application/components/field_name/field_name.test.tsx @@ -32,9 +32,7 @@ test('FieldName renders a number field by providing a field record, useShortDots expect(component).toMatchSnapshot(); }); -test('FieldName renders a geo field, useShortDots is set to true', () => { - const component = render( - - ); +test('FieldName renders a geo field', () => { + const component = render(); expect(component).toMatchSnapshot(); }); diff --git a/src/plugins/discover/public/application/components/field_name/field_name.tsx b/src/plugins/discover/public/application/components/field_name/field_name.tsx index cf11f971ef76c..b8f664d6cf38a 100644 --- a/src/plugins/discover/public/application/components/field_name/field_name.tsx +++ b/src/plugins/discover/public/application/components/field_name/field_name.tsx @@ -18,30 +18,31 @@ */ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; - import { FieldIcon, FieldIconProps } from '../../../../../kibana_react/public'; -import { shortenDottedString } from '../../helpers'; import { getFieldTypeName } from './field_type_name'; +import { FieldMapping } from '../../doc_views/doc_views_types'; // properties fieldType and fieldName are provided in kbn_doc_view // this should be changed when both components are deangularized interface Props { fieldName: string; fieldType: string; - useShortDots?: boolean; + fieldMapping?: FieldMapping; fieldIconProps?: Omit; scripted?: boolean; } export function FieldName({ fieldName, + fieldMapping, fieldType, - useShortDots, fieldIconProps, scripted = false, }: Props) { const typeName = getFieldTypeName(fieldType); - const displayName = useShortDots ? shortenDottedString(fieldName) : fieldName; + const displayName = + fieldMapping && fieldMapping.displayName ? fieldMapping.displayName : fieldName; + const tooltip = displayName !== fieldName ? `${fieldName} (${displayName})` : fieldName; return ( @@ -51,7 +52,7 @@ export function FieldName({ diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx index 02ed17cd01f07..391e15485f074 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx @@ -43,8 +43,6 @@ jest.mock('../../../kibana_services', () => ({ get: (key: string) => { if (key === 'fields:popularLimit') { return 5; - } else if (key === 'shortDots:enable') { - return false; } }, }, @@ -54,7 +52,6 @@ jest.mock('../../../kibana_services', () => ({ function getComponent({ selected = false, showDetails = false, - useShortDots = false, field, }: { selected?: boolean; @@ -72,19 +69,16 @@ function getComponent({ const finalField = field ?? - new IndexPatternField( - { - name: 'bytes', - type: 'number', - esTypes: ['long'], - count: 10, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - 'bytes' - ); + new IndexPatternField({ + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }); const props = { indexPattern, @@ -95,7 +89,6 @@ function getComponent({ onRemoveField: jest.fn(), showDetails, selected, - useShortDots, }; const comp = mountWithIntl(); return { comp, props }; @@ -118,17 +111,14 @@ describe('discover sidebar field', function () { expect(props.getDetails).toHaveBeenCalledWith(props.field); }); it('should not allow clicking on _source', function () { - const field = new IndexPatternField( - { - name: '_source', - type: '_source', - esTypes: ['_source'], - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - '_source' - ); + const field = new IndexPatternField({ + name: '_source', + type: '_source', + esTypes: ['_source'], + searchable: true, + aggregatable: true, + readFromDocValues: true, + }); const { comp, props } = getComponent({ selected: true, field, diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx index 0329b3a34580c..35515a6a0e7a5 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx @@ -24,7 +24,6 @@ import { DiscoverFieldDetails } from './discover_field_details'; import { FieldIcon, FieldButton } from '../../../../../kibana_react/public'; import { FieldDetails } from './types'; import { IndexPatternField, IndexPattern } from '../../../../../data/public'; -import { shortenDottedString } from '../../helpers'; import { getFieldTypeName } from './lib/get_field_type_name'; import './discover_field.scss'; @@ -58,10 +57,6 @@ export interface DiscoverFieldProps { * Determines whether the field is selected */ selected?: boolean; - /** - * Determines whether the field name is shortened test.sub1.sub2 = t.s.sub2 - */ - useShortDots?: boolean; /** * Metric tracking function * @param metricType @@ -78,7 +73,6 @@ export function DiscoverField({ onAddFilter, getDetails, selected, - useShortDots, trackUiMetric, }: DiscoverFieldProps) { const addLabelAria = i18n.translate('discover.fieldChooser.discoverField.addButtonAriaLabel', { @@ -118,13 +112,12 @@ export function DiscoverField({ ); + const title = + field.displayName !== field.name ? `${field.name} (${field.displayName} )` : field.displayName; + const fieldName = ( - - {useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)} + + {wrapOnDot(field.displayName)} ); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx index 8607873b98d3d..0618e53d15dbb 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx @@ -48,55 +48,46 @@ describe('discover sidebar field details', function () { } it('should enable the visualize link for a number field', function () { - const visualizableField = new IndexPatternField( - { - name: 'bytes', - type: 'number', - esTypes: ['long'], - count: 10, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - 'bytes' - ); + const visualizableField = new IndexPatternField({ + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }); const comp = mountComponent(visualizableField); expect(findTestSubject(comp, 'fieldVisualize-bytes')).toBeTruthy(); }); it('should disable the visualize link for an _id field', function () { - const conflictField = new IndexPatternField( - { - name: '_id', - type: 'string', - esTypes: ['_id'], - count: 0, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - 'test' - ); + const conflictField = new IndexPatternField({ + name: '_id', + type: 'string', + esTypes: ['_id'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }); const comp = mountComponent(conflictField); expect(findTestSubject(comp, 'fieldVisualize-_id')).toEqual({}); }); it('should disable the visualize link for an unknown field', function () { - const unknownField = new IndexPatternField( - { - name: 'test', - type: 'unknown', - esTypes: ['double'], - count: 0, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, - 'test' - ); + const unknownField = new IndexPatternField({ + name: 'test', + type: 'unknown', + esTypes: ['double'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }); const comp = mountComponent(unknownField); expect(findTestSubject(comp, 'fieldVisualize-test')).toEqual({}); }); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx index 7504d181d82b2..23d2fa0a39f34 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx @@ -51,8 +51,6 @@ jest.mock('../../../kibana_services', () => ({ get: (key: string) => { if (key === 'fields:popularLimit') { return 5; - } else if (key === 'shortDots:enable') { - return false; } }, }, diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index dfd09ccee9337..b8e09ce4d17e8 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -30,7 +30,7 @@ import { IndexPatternAttributes } from '../../../../../data/common'; import { SavedObject } from '../../../../../../core/types'; import { FIELDS_LIMIT_SETTING } from '../../../../common'; import { groupFields } from './lib/group_fields'; -import { IndexPatternField, IndexPattern, UI_SETTINGS } from '../../../../../data/public'; +import { IndexPatternField, IndexPattern } from '../../../../../data/public'; import { getDetails } from './lib/get_details'; import { getDefaultFieldFilter, setFieldFilterProp } from './lib/field_filter'; import { getIndexPatternFieldList } from './lib/get_index_pattern_field_list'; @@ -117,7 +117,6 @@ export function DiscoverSidebar({ ); const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING); - const useShortDots = services.uiSettings.get(UI_SETTINGS.SHORT_DOTS_ENABLE); const { selected: selectedFields, @@ -201,7 +200,6 @@ export function DiscoverSidebar({ onAddFilter={onAddFilter} getDetails={getDetailsByField} selected={true} - useShortDots={useShortDots} trackUiMetric={trackUiMetric} /> @@ -276,7 +274,6 @@ export function DiscoverSidebar({ onRemoveField={onRemoveField} onAddFilter={onAddFilter} getDetails={getDetailsByField} - useShortDots={useShortDots} trackUiMetric={trackUiMetric} /> @@ -307,7 +304,6 @@ export function DiscoverSidebar({ onRemoveField={onRemoveField} onAddFilter={onAddFilter} getDetails={getDetailsByField} - useShortDots={useShortDots} trackUiMetric={trackUiMetric} /> diff --git a/src/plugins/discover/public/application/components/sidebar/lib/field_filter.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/field_filter.test.ts index eb139f97c7b00..ebbffae83125c 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/field_filter.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/field_filter.test.ts @@ -59,6 +59,7 @@ describe('field_filter', function () { const fieldList = [ { name: 'bytes', + displayName: 'Bye,bye,Bytes', type: 'number', esTypes: ['long'], count: 10, @@ -68,6 +69,7 @@ describe('field_filter', function () { }, { name: 'extension', + displayName: 'Extension', type: 'string', esTypes: ['text'], count: 10, @@ -80,6 +82,8 @@ describe('field_filter', function () { [ { filter: {}, result: ['bytes', 'extension'] }, { filter: { name: 'by' }, result: ['bytes'] }, + { filter: { name: 'Ext' }, result: ['extension'] }, + { filter: { name: 'Bytes' }, result: ['bytes'] }, { filter: { aggregatable: true }, result: ['extension'] }, { filter: { aggregatable: true, searchable: false }, result: [] }, { filter: { type: 'string' }, result: ['extension'] }, diff --git a/src/plugins/discover/public/application/components/sidebar/lib/field_filter.ts b/src/plugins/discover/public/application/components/sidebar/lib/field_filter.ts index f0d9a2d8af20f..2e1d9b76606df 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/field_filter.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/field_filter.ts @@ -72,7 +72,9 @@ export function isFieldFiltered( field.type === '_source' || field.scripted || fieldCounts[field.name] > 0; - const matchName = !filterState.name || field.name.indexOf(filterState.name) !== -1; + const needle = filterState.name ? filterState.name.toLowerCase() : ''; + const haystack = `${field.name}${field.displayName || ''}`.toLowerCase(); + const matchName = !filterState.name || haystack.indexOf(needle) !== -1; return matchFilter && isAggregatable && isSearchable && scriptedOrMissing && matchName; } diff --git a/src/plugins/discover/public/application/components/table/table.tsx b/src/plugins/discover/public/application/components/table/table.tsx index 628045bd32f61..5d37f598b38f6 100644 --- a/src/plugins/discover/public/application/components/table/table.tsx +++ b/src/plugins/discover/public/application/components/table/table.tsx @@ -46,7 +46,13 @@ export function DocViewTable({ {Object.keys(flattened) - .sort() + .sort((fieldA, fieldB) => { + const mappingA = mapping(fieldA); + const mappingB = mapping(fieldB); + const nameA = !mappingA || !mappingA.displayName ? fieldA : mappingA.displayName; + const nameB = !mappingB || !mappingB.displayName ? fieldB : mappingB.displayName; + return nameA.localeCompare(nameB); + }) .map((field) => { const valueRaw = flattened[field]; const value = trimAngularSpan(String(formatted[field])); diff --git a/src/plugins/discover/public/application/components/table/table_row.tsx b/src/plugins/discover/public/application/components/table/table_row.tsx index 5f7dd9f37dcd3..3d75e175951d5 100644 --- a/src/plugins/discover/public/application/components/table/table_row.tsx +++ b/src/plugins/discover/public/application/components/table/table_row.tsx @@ -91,6 +91,7 @@ export function DocViewTableRow({ diff --git a/src/plugins/discover/public/application/doc_views/doc_views_types.ts b/src/plugins/discover/public/application/doc_views/doc_views_types.ts index 6c90861e26727..01145402e0f29 100644 --- a/src/plugins/discover/public/application/doc_views/doc_views_types.ts +++ b/src/plugins/discover/public/application/doc_views/doc_views_types.ts @@ -36,6 +36,7 @@ export interface FieldMapping { rowCount?: number; type: string; name: string; + displayName?: string; } export type DocViewFilterFn = ( diff --git a/src/plugins/discover/public/application/helpers/index.ts b/src/plugins/discover/public/application/helpers/index.ts index 3555d24924e80..f7497c29a2bda 100644 --- a/src/plugins/discover/public/application/helpers/index.ts +++ b/src/plugins/discover/public/application/helpers/index.ts @@ -17,5 +17,4 @@ * under the License. */ -export { shortenDottedString } from './shorten_dotted_string'; export { formatNumWithCommas } from './format_number_with_commas'; diff --git a/src/plugins/discover/public/application/helpers/shorten_dotted_string.ts b/src/plugins/discover/public/application/helpers/shorten_dotted_string.ts deleted file mode 100644 index 9d78a96784339..0000000000000 --- a/src/plugins/discover/public/application/helpers/shorten_dotted_string.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const DOT_PREFIX_RE = /(.).+?\./g; - -/** - * Convert a dot.notated.string into a short - * version (d.n.string) - */ -export const shortenDottedString = (input: string) => input.replace(DOT_PREFIX_RE, '$1.'); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx index e1359eafe1c67..4b63eb5c56fd1 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx @@ -19,7 +19,14 @@ import React, { PureComponent } from 'react'; -import { EuiIcon, EuiInMemoryTable, EuiIconTip, EuiBasicTableColumn } from '@elastic/eui'; +import { + EuiIcon, + EuiInMemoryTable, + EuiIconTip, + EuiBasicTableColumn, + EuiBadge, + EuiToolTip, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -144,6 +151,11 @@ const editDescription = i18n.translate( { defaultMessage: 'Edit' } ); +const customNameDescription = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.table.customNameTooltip', + { defaultMessage: 'A custom name for the field.' } +); + interface IndexedFieldProps { indexPattern: IIndexPattern; items: IndexedFieldItem[]; @@ -160,7 +172,7 @@ export class Table extends PureComponent { return ( - {name} + {field.name} {field.info && field.info.length ? (   @@ -185,6 +197,15 @@ export class Table extends PureComponent { /> ) : null} + {field.customName && field.customName !== field.name ? ( +
+ + + {field.customName} + + +
+ ) : null}
); } diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx index 23f0a83c591de..1a04aaf784839 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx @@ -47,10 +47,7 @@ const indexPattern = ({ } as unknown) as IndexPattern; const mockFieldToIndexPatternField = (spec: Record) => { - return new IndexPatternField( - (spec as unknown) as IndexPatternField['spec'], - spec.displayName as string - ); + return new IndexPatternField((spec as unknown) as IndexPatternField['spec']); }; const fields = [ diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 92f0c4576e931..e097271248bbd 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -89,7 +89,11 @@ export class IndexedFieldsTable extends Component< (fields, fieldFilter, indexedFieldTypeFilter) => { if (fieldFilter) { const normalizedFieldFilter = fieldFilter.toLowerCase(); - fields = fields.filter((field) => field.name.toLowerCase().includes(normalizedFieldFilter)); + fields = fields.filter( + (field) => + field.name.toLowerCase().includes(normalizedFieldFilter) || + (field.displayName && field.displayName.toLowerCase().includes(normalizedFieldFilter)) + ); } if (indexedFieldTypeFilter) { diff --git a/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap index 1e8fb6f9492fe..babfbbfc2a763 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap @@ -51,6 +51,22 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = ` value="" /> + + } + label="Custom name" + > + + - + + } + label="Custom name" + > + + - + + } + label="Custom name" + > + + - + + } + label="Custom name" + > + + @@ -954,7 +1021,7 @@ exports[`FieldEditor should show deprecated lang warning 1`] = ` isInvalid={false} label="Script" > - + + } + label="Custom name" + > + + - ({})); @@ -37,6 +38,7 @@ jest.mock('@elastic/eui', () => ({ EuiButtonEmpty: 'eui-button-empty', EuiCallOut: 'eui-call-out', EuiCode: 'eui-code', + EuiCodeEditor: 'eui-code-editor', EuiConfirmModal: 'eui-confirm-modal', EuiFieldNumber: 'eui-field-number', EuiFieldText: 'eui-field-text', @@ -173,6 +175,60 @@ describe('FieldEditor', () => { expect(component).toMatchSnapshot(); }); + it('should display and update a customName correctly', async () => { + let testField = ({ + name: 'test', + format: new Format(), + lang: undefined, + type: 'string', + customName: 'Test', + } as unknown) as IndexPatternField; + fieldList.push(testField); + indexPattern.fields.getByName = (name) => { + const flds = { + [testField.name]: testField, + }; + return flds[name]; + }; + indexPattern.fields = { + ...indexPattern.fields, + ...{ + update: (fld) => { + testField = (fld as unknown) as IndexPatternField; + }, + add: jest.fn(), + }, + }; + indexPattern.fieldFormatMap = { test: field }; + indexPattern.deleteFieldFormat = jest.fn(); + + const component = createComponentWithContext( + FieldEditor, + { + indexPattern, + spec: (testField as unknown) as IndexPatternField, + services: { + redirectAway: () => {}, + indexPatternService: ({ + updateSavedObject: jest.fn(() => Promise.resolve()), + } as unknown) as IndexPatternsService, + }, + }, + mockContext + ); + + await new Promise((resolve) => process.nextTick(resolve)); + component.update(); + const input = findTestSubject(component, 'editorFieldCustomName'); + expect(input.props().value).toBe('Test'); + input.simulate('change', { target: { value: 'new Test' } }); + const saveBtn = findTestSubject(component, 'fieldSaveButton'); + + await saveBtn.simulate('click'); + await new Promise((resolve) => process.nextTick(resolve)); + expect(testField.customName).toEqual('new Test'); + }); + it('should show deprecated lang warning', async () => { const testField = { ...field, diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx index d02338a6aee24..97d30d88e018c 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx @@ -126,6 +126,7 @@ export interface FieldEditorState { errors?: string[]; format: any; spec: IndexPatternField['spec']; + customName: string; } export interface FieldEdiorProps { @@ -166,6 +167,7 @@ export class FieldEditor extends PureComponent + } + > + { + this.setState({ customName: e.target.value }); + }} + /> + + ); + } + /** * renders a warning and a table of conflicting indices * in case there are indices with different types @@ -772,7 +802,7 @@ export class FieldEditor extends PureComponent { const field = this.state.spec; const { indexPattern } = this.props; - const { fieldFormatId, fieldFormatParams } = this.state; + const { fieldFormatId, fieldFormatParams, customName } = this.state; if (field.scripted) { this.setState({ @@ -813,6 +843,11 @@ export class FieldEditor extends PureComponent { @@ -873,6 +908,7 @@ export class FieldEditor extends PureComponent {this.renderScriptingPanels()} {this.renderName()} + {this.renderCustomName()} {this.renderLanguage()} {this.renderType()} {this.renderTypeConflict()} diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 3c9996ca44ff8..fe5c04c001731 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -293,6 +293,16 @@ export default function ({ getService, getPageObjects }) { const currentUrlWithoutScore = await browser.getCurrentUrl(); expect(currentUrlWithoutScore).not.to.contain('_score'); }); + it('should add a field with customLabel, sort by it, display it correctly', async function () { + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.clickFieldListItemAdd('referer'); + await PageObjects.discover.clickFieldSort('referer'); + expect(await PageObjects.discover.getDocHeader()).to.have.string('Referer custom'); + expect(await PageObjects.discover.getAllFieldNames()).to.contain('Referer custom'); + const url = await browser.getCurrentUrl(); + expect(url).to.contain('referer'); + }); }); describe('refresh interval', function () { diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js index bd7511d373b90..5b0b7af56b332 100644 --- a/test/functional/apps/visualize/_data_table.js +++ b/test/functional/apps/visualize/_data_table.js @@ -209,6 +209,29 @@ export default function ({ getService, getPageObjects }) { ]); }); + it('should show correct data when selecting a field by its custom name', async () => { + await PageObjects.visualize.navigateToNewAggBasedVisualization(); + await PageObjects.visualize.clickDataTable(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectField('UTC time'); + await PageObjects.visEditor.setInterval('Day'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisData(); + expect(data.trim().split('\n')).to.be.eql([ + '2015-09-20', + '4,757', + '2015-09-21', + '4,614', + '2015-09-22', + '4,633', + ]); + const header = await PageObjects.visChart.getTableVisHeader(); + expect(header).to.contain('UTC time'); + }); + it('should correctly filter for applied time filter on the main timefield', async () => { await filterBar.addFilter('@timestamp', 'is between', '2015-09-19', '2015-09-21'); await PageObjects.visChart.waitForVisualizationRenderingStabilized(); diff --git a/test/functional/fixtures/es_archiver/discover/data.json b/test/functional/fixtures/es_archiver/discover/data.json index 9158a3023fc5e..0f9820a6c2f6e 100644 --- a/test/functional/fixtures/es_archiver/discover/data.json +++ b/test/functional/fixtures/es_archiver/discover/data.json @@ -7,7 +7,8 @@ "index-pattern": { "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"@message\"}}},{\"name\":\"@tags\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"@tags\"}}},{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"headings\"}}},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"host\"}}},{\"name\":\"id\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"links\"}}},{\"name\":\"machine.os\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"esTypes\":[\"double\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nestedField.child\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"nested\":{\"path\":\"nestedField\"}}},{\"name\":\"phpmemory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.article:section\"}}},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.article:tag\"}}},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:description\"}}},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:image\"}}},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:image:height\"}}},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:image:width\"}}},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:site_name\"}}},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:title\"}}},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:type\"}}},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:url\"}}},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:card\"}}},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:description\"}}},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:image\"}}},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:site\"}}},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:title\"}}},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.url\"}}},{\"name\":\"request\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"response\"}}},{\"name\":\"spaces\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"spaces\"}}},{\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"xss\"}}}]", "timeFieldName": "@timestamp", - "title": "logstash-*" + "title": "logstash-*", + "fieldAttrs": "{\"referer\":{\"customName\":\"Referer custom\"}}" }, "type": "index-pattern" } diff --git a/test/functional/fixtures/es_archiver/discover/mappings.json b/test/functional/fixtures/es_archiver/discover/mappings.json index 82002c095bcc5..53bbe8a5baa5b 100644 --- a/test/functional/fixtures/es_archiver/discover/mappings.json +++ b/test/functional/fixtures/es_archiver/discover/mappings.json @@ -93,6 +93,9 @@ }, "title": { "type": "text" + }, + "fieldAttrs": { + "type": "text" } } }, diff --git a/test/functional/fixtures/es_archiver/visualize/data.json b/test/functional/fixtures/es_archiver/visualize/data.json index abca5a98bf7fd..c57cdb40ae952 100644 --- a/test/functional/fixtures/es_archiver/visualize/data.json +++ b/test/functional/fixtures/es_archiver/visualize/data.json @@ -8,7 +8,8 @@ "fieldFormatMap": "{\"bytes\":{\"id\":\"bytes\"}}", "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", "timeFieldName": "@timestamp", - "title": "logstash-*" + "title": "logstash-*", + "fieldAttrs": "{\"utc_time\":{\"customName\":\"UTC time\"}}" }, "type": "index-pattern" } diff --git a/test/functional/fixtures/es_archiver/visualize/mappings.json b/test/functional/fixtures/es_archiver/visualize/mappings.json index a50aed233eea6..464f6751eac5c 100644 --- a/test/functional/fixtures/es_archiver/visualize/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize/mappings.json @@ -93,6 +93,9 @@ }, "title": { "type": "text" + }, + "fieldAttrs": { + "type": "text" } } }, diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 2423f66a4b34e..9c5bedf7c242d 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -246,9 +246,9 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider public async getAllFieldNames() { const sidebar = await testSubjects.find('discover-sidebar'); const $ = await sidebar.parseDomContent(); - return $('.dscSidebar__item[attr-field]') + return $('.dscSidebarField__name') .toArray() - .map((field) => $(field).find('span.eui-textTruncate').text()); + .map((field) => $(field).text()); } public async getSidebarWidth() { diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index 1acea624ad4cd..3e3f60ca17131 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -328,6 +328,13 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr return await testSubjects.getVisibleText('paginated-table-body'); } + /** + * This function returns the text displayed in the Table Vis header + */ + public async getTableVisHeader() { + return await testSubjects.getVisibleText('paginated-table-header'); + } + /** * This function is the newer function to retrieve data from within a table visualization. * It uses a better return format, than the old getTableVisData, by properly splitting diff --git a/x-pack/test/functional/es_archives/discover/default/mappings.json b/x-pack/test/functional/es_archives/discover/default/mappings.json index 82002c095bcc5..53bbe8a5baa5b 100644 --- a/x-pack/test/functional/es_archives/discover/default/mappings.json +++ b/x-pack/test/functional/es_archives/discover/default/mappings.json @@ -93,6 +93,9 @@ }, "title": { "type": "text" + }, + "fieldAttrs": { + "type": "text" } } }, From ee81b5fc04c6337c94d74684e7ed8620915f0644 Mon Sep 17 00:00:00 2001 From: ymao1 Date: Tue, 17 Nov 2020 07:29:10 -0500 Subject: [PATCH 05/10] [Alerting UI] Fix console error when setting connector params (#83333) * Fixing console errors * Setting defaults for undefined inputs in text area/field with message variables * Cleanup * Cleanup * Fixing pagerduty timestamp validation * Fixing test * Pagerduty params * Reverting unnecessary changes --- .../components/text_area_with_message_variables.tsx | 2 +- .../components/text_field_with_message_variables.tsx | 2 +- .../sections/action_connector_form/connector_add_flyout.tsx | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx index e60785f70bffe..f5095101d96b5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx @@ -63,7 +63,7 @@ export const TextAreaWithMessageVariables: React.FunctionComponent = ({ fullWidth isInvalid={errors && errors.length > 0 && inputTargetValue !== undefined} name={paramsProperty} - value={inputTargetValue} + value={inputTargetValue || ''} data-test-subj={`${paramsProperty}TextArea`} onChange={(e: React.ChangeEvent) => onChangeWithMessageVariable(e)} onFocus={(e: React.FocusEvent) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.tsx index fc05b237ccf5e..946bf064eb9ce 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.tsx @@ -50,7 +50,7 @@ export const TextFieldWithMessageVariables: React.FunctionComponent = ({ id={`${paramsProperty}Id`} isInvalid={errors && errors.length > 0 && inputTargetValue !== undefined} data-test-subj={`${paramsProperty}Input`} - value={inputTargetValue} + value={inputTargetValue || ''} onChange={(e: React.ChangeEvent) => onChangeWithMessageVariable(e)} onFocus={(e: React.FocusEvent) => { setCurrentTextElement(e.target); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index 00ff6fc132cdc..b53d0816ea068 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -72,10 +72,8 @@ export const ConnectorAddFlyout = ({ const [isSaving, setIsSaving] = useState(false); const closeFlyout = useCallback(() => { - setActionType(undefined); - setConnector(initialConnector); onClose(); - }, [onClose, initialConnector]); + }, [onClose]); const canSave = hasSaveActionsCapability(capabilities); From 9b5605f4c412c25d9831ba35ea756780ddebdbf7 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Tue, 17 Nov 2020 08:09:21 -0500 Subject: [PATCH 06/10] [App Search] Added all Document related routes and logic (#83324) --- .../documents/document_detail_logic.test.ts | 156 ++++++++++++++++++ .../documents/document_detail_logic.ts | 92 +++++++++++ .../documents/documents_logic.test.ts | 66 ++++++++ .../components/documents/documents_logic.ts | 35 ++++ .../app_search/components/documents/index.ts | 8 + .../app_search/components/documents/types.ts | 11 ++ .../routes/app_search/documents.test.ts | 61 +++++++ .../server/routes/app_search/documents.ts | 47 ++++++ .../server/routes/app_search/index.ts | 2 + 9 files changed, 478 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/types.ts create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts 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 new file mode 100644 index 0000000000000..782b8159c94a1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resetContext } from 'kea'; + +import { mockHttpValues } from '../../../__mocks__'; +jest.mock('../../../shared/http', () => ({ + HttpLogic: { values: mockHttpValues }, +})); +const { http } = mockHttpValues; + +jest.mock('../engine', () => ({ + EngineLogic: { values: { engineName: 'engine1' } }, +})); + +jest.mock('../../../shared/flash_messages', () => ({ + setQueuedSuccessMessage: jest.fn(), + flashAPIErrors: jest.fn(), +})); +import { setQueuedSuccessMessage, flashAPIErrors } from '../../../shared/flash_messages'; + +import { DocumentDetailLogic } from './document_detail_logic'; + +describe('DocumentDetailLogic', () => { + const DEFAULT_VALUES = { + dataLoading: true, + fields: [], + }; + + const mount = (defaults?: object) => { + if (!defaults) { + resetContext({}); + } else { + resetContext({ + defaults: { + enterprise_search: { + app_search: { + document_detail_logic: { + ...defaults, + }, + }, + }, + }, + }); + } + DocumentDetailLogic.mount(); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('actions', () => { + describe('setFields', () => { + it('should set fields to the provided value and dataLoading to false', () => { + const fields = [{ name: 'foo', value: ['foo'], type: 'string' }]; + + mount({ + dataLoading: true, + fields: [], + }); + + DocumentDetailLogic.actions.setFields(fields); + + expect(DocumentDetailLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: false, + fields, + }); + }); + }); + + describe('getDocumentDetails', () => { + it('will call an API endpoint and then store the result', async () => { + const fields = [{ name: 'name', value: 'python', type: 'string' }]; + jest.spyOn(DocumentDetailLogic.actions, 'setFields'); + const promise = Promise.resolve({ fields }); + http.get.mockReturnValue(promise); + + DocumentDetailLogic.actions.getDocumentDetails('1'); + + expect(http.get).toHaveBeenCalledWith(`/api/app_search/engines/engine1/documents/1`); + await promise; + expect(DocumentDetailLogic.actions.setFields).toHaveBeenCalledWith(fields); + }); + + it('handles errors', async () => { + mount(); + const promise = Promise.reject('An error occurred'); + http.get.mockReturnValue(promise); + + try { + DocumentDetailLogic.actions.getDocumentDetails('1'); + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('An error occurred'); + }); + }); + + describe('deleteDocument', () => { + let confirmSpy: any; + let promise: Promise; + + beforeEach(() => { + confirmSpy = jest.spyOn(window, 'confirm'); + confirmSpy.mockImplementation(jest.fn(() => true)); + promise = Promise.resolve({}); + http.delete.mockReturnValue(promise); + }); + + afterEach(() => { + confirmSpy.mockRestore(); + }); + + it('will call an API endpoint and show a success message', async () => { + mount(); + DocumentDetailLogic.actions.deleteDocument('1'); + + expect(http.delete).toHaveBeenCalledWith(`/api/app_search/engines/engine1/documents/1`); + await promise; + expect(setQueuedSuccessMessage).toHaveBeenCalledWith( + 'Successfully marked document for deletion. It will be deleted momentarily.' + ); + }); + + it('will do nothing if not confirmed', async () => { + mount(); + window.confirm = () => false; + + DocumentDetailLogic.actions.deleteDocument('1'); + + expect(http.delete).not.toHaveBeenCalled(); + await promise; + }); + + it('handles errors', async () => { + mount(); + promise = Promise.reject('An error occured'); + http.delete.mockReturnValue(promise); + + try { + DocumentDetailLogic.actions.deleteDocument('1'); + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); + }); + }); + }); +}); 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 new file mode 100644 index 0000000000000..87bf149fb1680 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { kea, MakeLogicType } from 'kea'; +import { i18n } from '@kbn/i18n'; + +import { HttpLogic } from '../../../shared/http'; +import { EngineLogic } from '../engine'; +import { flashAPIErrors, setQueuedSuccessMessage } from '../../../shared/flash_messages'; +import { FieldDetails } from './types'; + +interface DocumentDetailLogicValues { + dataLoading: boolean; + fields: FieldDetails[]; +} + +interface DocumentDetailLogicActions { + setFields(fields: FieldDetails[]): { fields: FieldDetails[] }; + deleteDocument(documentId: string): { documentId: string }; + getDocumentDetails(documentId: string): { documentId: string }; +} + +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: () => ({ + setFields: (fields) => ({ fields }), + getDocumentDetails: (documentId) => ({ documentId }), + deleteDocument: (documentId) => ({ documentId }), + }), + reducers: () => ({ + dataLoading: [ + true, + { + setFields: () => false, + }, + ], + fields: [ + [], + { + setFields: (_, { fields }) => fields, + }, + ], + }), + listeners: ({ actions }) => ({ + getDocumentDetails: async ({ documentId }) => { + const { engineName } = EngineLogic.values; + + try { + const { http } = HttpLogic.values; + // TODO: Handle 404s + const response = await http.get( + `/api/app_search/engines/${engineName}/documents/${documentId}` + ); + actions.setFields(response.fields); + } catch (e) { + flashAPIErrors(e); + } + }, + deleteDocument: async ({ documentId }) => { + const { engineName } = EngineLogic.values; + + if (window.confirm(CONFIRM_DELETE)) { + try { + const { http } = HttpLogic.values; + await http.delete(`/api/app_search/engines/${engineName}/documents/${documentId}`); + setQueuedSuccessMessage(DELETE_SUCCESS); + // TODO Handle routing after success + } catch (e) { + flashAPIErrors(e); + } + } + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.test.ts new file mode 100644 index 0000000000000..236172f0f7bdf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.test.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resetContext } from 'kea'; + +import { DocumentsLogic } from './documents_logic'; + +describe('DocumentsLogic', () => { + const DEFAULT_VALUES = { + isDocumentCreationOpen: false, + }; + + const mount = (defaults?: object) => { + if (!defaults) { + resetContext({}); + } else { + resetContext({ + defaults: { + enterprise_search: { + app_search: { + documents_logic: { + ...defaults, + }, + }, + }, + }, + }); + } + DocumentsLogic.mount(); + }; + + describe('actions', () => { + describe('openDocumentCreation', () => { + it('should toggle isDocumentCreationOpen to true', () => { + mount({ + isDocumentCreationOpen: false, + }); + + DocumentsLogic.actions.openDocumentCreation(); + + expect(DocumentsLogic.values).toEqual({ + ...DEFAULT_VALUES, + isDocumentCreationOpen: true, + }); + }); + }); + + describe('closeDocumentCreation', () => { + it('should toggle isDocumentCreationOpen to false', () => { + mount({ + isDocumentCreationOpen: true, + }); + + DocumentsLogic.actions.closeDocumentCreation(); + + expect(DocumentsLogic.values).toEqual({ + ...DEFAULT_VALUES, + isDocumentCreationOpen: false, + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.ts new file mode 100644 index 0000000000000..dcf1a883bd3b6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { kea, MakeLogicType } from 'kea'; + +interface DocumentsLogicValues { + isDocumentCreationOpen: boolean; +} + +interface DocumentsLogicActions { + closeDocumentCreation(): void; + openDocumentCreation(): void; +} + +type DocumentsLogicType = MakeLogicType; + +export const DocumentsLogic = kea({ + path: ['enterprise_search', 'app_search', 'documents_logic'], + actions: () => ({ + openDocumentCreation: true, + closeDocumentCreation: true, + }), + reducers: () => ({ + isDocumentCreationOpen: [ + false, + { + openDocumentCreation: () => true, + closeDocumentCreation: () => false, + }, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/index.ts new file mode 100644 index 0000000000000..d374098d70788 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/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 { DocumentDetailLogic } from './document_detail_logic'; +export { DocumentsLogic } from './documents_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/types.ts new file mode 100644 index 0000000000000..6a7c1cd1d5d2f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/types.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. + */ + +export interface FieldDetails { + name: string; + value: string | string[]; + type: string; +} 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 new file mode 100644 index 0000000000000..d5fed4c6f97cb --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__'; + +import { registerDocumentRoutes } from './documents'; + +describe('document routes', () => { + describe('GET /api/app_search/engines/{engineName}/documents/{documentId}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines/{engineName}/documents/{documentId}', + }); + + registerDocumentRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + 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', + }); + }); + }); + + describe('DELETE /api/app_search/engines/{engineName}/documents/{documentId}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'delete', + path: '/api/app_search/engines/{engineName}/documents/{documentId}', + }); + + registerDocumentRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + 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', + }); + }); + }); +}); 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 new file mode 100644 index 0000000000000..a2f4b323a91aa --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.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 { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../plugin'; + +export function registerDocumentRoutes({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/app_search/engines/{engineName}/documents/{documentId}', + validate: { + params: schema.object({ + engineName: schema.string(), + documentId: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/${request.params.engineName}/documents/${request.params.documentId}`, + })(context, request, response); + } + ); + router.delete( + { + path: '/api/app_search/engines/{engineName}/documents/{documentId}', + validate: { + params: schema.object({ + engineName: schema.string(), + documentId: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/${request.params.engineName}/documents/${request.params.documentId}`, + })(context, request, response); + } + ); +} diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index faf74203cf17d..f64e45c656fa1 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -9,9 +9,11 @@ import { RouteDependencies } from '../../plugin'; import { registerEnginesRoutes } from './engines'; import { registerCredentialsRoutes } from './credentials'; import { registerSettingsRoutes } from './settings'; +import { registerDocumentRoutes } from './documents'; export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerEnginesRoutes(dependencies); registerCredentialsRoutes(dependencies); registerSettingsRoutes(dependencies); + registerDocumentRoutes(dependencies); }; From e4516ee0e9eb2a0b36219c10ce7634444039ebd6 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 17 Nov 2020 07:37:46 -0600 Subject: [PATCH 07/10] [Workplace Search] Enable check for org context based on URL (#83487) * Add regex check to determine whether url is org As a part of the Kibana migration, we are switching the URL structure to put the prefix on the personal dashboard. In ent-search, org routes were prefixed with `/org`. In Kibana the prefix switches to non-org routes and they will be prefixed with`/p` * Add isOrganization boolean to logic --- .../workplace_search/app_logic.test.ts | 10 ++++++++++ .../workplace_search/app_logic.ts | 9 +++++++++ .../workplace_search/index.test.tsx | 10 ++++------ .../applications/workplace_search/index.tsx | 19 +++++++++++++++++-- 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts index 974e07069ddba..d77faf471facc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts @@ -19,6 +19,7 @@ describe('AppLogic', () => { account: {}, hasInitialized: false, isFederatedAuth: true, + isOrganization: false, organization: {}, }; @@ -34,6 +35,7 @@ describe('AppLogic', () => { }, hasInitialized: true, isFederatedAuth: false, + isOrganization: false, organization: { defaultOrgName: 'My Organization', name: 'ACME Donuts', @@ -61,4 +63,12 @@ describe('AppLogic', () => { }); }); }); + + describe('setContext()', () => { + it('sets context', () => { + AppLogic.actions.setContext(true); + + expect(AppLogic.values.isOrganization).toEqual(true); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts index b7476a5187749..f5f534807fabf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts @@ -16,9 +16,11 @@ import { interface AppValues extends WorkplaceSearchInitialData { hasInitialized: boolean; isFederatedAuth: boolean; + isOrganization: boolean; } interface AppActions { initializeAppData(props: InitialAppData): InitialAppData; + setContext(isOrganization: boolean): boolean; } const emptyOrg = {} as Organization; @@ -31,6 +33,7 @@ export const AppLogic = kea>({ workplaceSearch, isFederatedAuth, }), + setContext: (isOrganization) => isOrganization, }, reducers: { hasInitialized: [ @@ -45,6 +48,12 @@ export const AppLogic = kea>({ initializeAppData: (_, { isFederatedAuth }) => !!isFederatedAuth, }, ], + isOrganization: [ + false, + { + setContext: (_, isOrganization) => isOrganization, + }, + ], organization: [ emptyOrg, { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx index 25544b4a9bb68..5f1e2dd18d3b6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx @@ -46,9 +46,12 @@ describe('WorkplaceSearchUnconfigured', () => { }); describe('WorkplaceSearchConfigured', () => { + const initializeAppData = jest.fn(); + const setContext = jest.fn(); + beforeEach(() => { jest.clearAllMocks(); - setMockActions({ initializeAppData: () => {} }); + setMockActions({ initializeAppData, setContext }); }); it('renders layout and header actions', () => { @@ -60,17 +63,12 @@ describe('WorkplaceSearchConfigured', () => { }); it('initializes app data with passed props', () => { - const initializeAppData = jest.fn(); - setMockActions({ initializeAppData }); - shallow(); expect(initializeAppData).toHaveBeenCalledWith({ isFederatedAuth: true }); }); it('does not re-initialize app data or re-render header actions', () => { - const initializeAppData = jest.fn(); - setMockActions({ initializeAppData }); setMockValues({ hasInitialized: true }); shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index 311f30a891eb9..776cae24dfdfb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -5,7 +5,7 @@ */ import React, { useEffect } from 'react'; -import { Route, Redirect, Switch } from 'react-router-dom'; +import { Route, Redirect, Switch, useLocation } from 'react-router-dom'; import { useActions, useValues } from 'kea'; import { WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants'; @@ -31,10 +31,21 @@ export const WorkplaceSearch: React.FC = (props) => { export const WorkplaceSearchConfigured: React.FC = (props) => { const { hasInitialized } = useValues(AppLogic); - const { initializeAppData } = useActions(AppLogic); + const { initializeAppData, setContext } = useActions(AppLogic); const { renderHeaderActions } = useValues(KibanaLogic); const { errorConnecting, readOnlyMode } = useValues(HttpLogic); + const { pathname } = useLocation(); + + /** + * Personal dashboard urls begin with /p/ + * EX: http://localhost:5601/app/enterprise_search/workplace_search/p/sources + */ + const personalSourceUrlRegex = /^\/p\//g; // matches '/p/*' + + // TODO: Once auth is figured out, we need to have a check for the equivilent of `isAdmin`. + const isOrganization = !pathname.match(personalSourceUrlRegex); + useEffect(() => { if (!hasInitialized) { initializeAppData(props); @@ -42,6 +53,10 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { } }, [hasInitialized]); + useEffect(() => { + setContext(isOrganization); + }, [isOrganization]); + return ( From 2fb04a6d41c7db1ddc74e5fe4ea7045eec0f7aa2 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 17 Nov 2020 14:44:06 +0100 Subject: [PATCH 08/10] [Uptime] Mock implementation to account for math flakiness test (#83535) --- .../lib/requests/__tests__/get_ping_histogram.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts index 86e5f2876ca28..427061b6c16d4 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts @@ -7,8 +7,13 @@ import { getPingHistogram } from '../get_ping_histogram'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; +import * as intervalHelper from '../../helper/get_histogram_interval'; describe('getPingHistogram', () => { + beforeEach(() => { + jest.spyOn(intervalHelper, 'getHistogramInterval').mockReturnValue(36000); + }); + const standardMockResponse: any = { aggregations: { timeseries: { @@ -36,7 +41,7 @@ describe('getPingHistogram', () => { }, }; - it.skip('returns a single bucket if array has 1', async () => { + it('returns a single bucket if array has 1', async () => { expect.assertions(2); const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); From 8b658fbcd2e97546b59a156df57b02d866882710 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 17 Nov 2020 06:44:54 -0800 Subject: [PATCH 09/10] Used SO for saving the API key IDs that should be deleted (#82211) * Used SO for saving the API key IDs that should be deleted and create a configuration option where can set an execution interval for a TM task which will get the data from this SO and remove marked for delete keys. * removed invalidateApiKey from AlertsClient * Fixed type checks * Fixed jest tests * Removed test code * Changed SO name * fixed type cheks * Moved invalidate logic out of alerts client * fixed type check * Added functional tests * Fixed due to comments * added configurable delay for invalidation task * added interval to the task response * Fixed jest tests * Fixed due to comments * Fixed task * fixed paging * Fixed date filter * Fixed jest tests * fixed due to comments * fixed due to comments * Fixed e2e test * Fixed e2e test * Fixed due to comments. Changed api key invalidation task to use SavedObjectClient * Use encryptedSavedObjectClient * set back flaky test comment --- .../server/alerts_client/alerts_client.ts | 82 ++++--- .../alerts_client/tests/aggregate.test.ts | 1 - .../server/alerts_client/tests/create.test.ts | 19 +- .../server/alerts_client/tests/delete.test.ts | 56 ++++- .../alerts_client/tests/disable.test.ts | 65 ++++- .../server/alerts_client/tests/enable.test.ts | 42 +++- .../server/alerts_client/tests/find.test.ts | 1 - .../server/alerts_client/tests/get.test.ts | 1 - .../tests/get_alert_instance_summary.test.ts | 1 - .../tests/get_alert_state.test.ts | 1 - .../alerts/server/alerts_client/tests/lib.ts | 8 - .../tests/list_alert_types.test.ts | 1 - .../alerts_client/tests/mute_all.test.ts | 1 - .../alerts_client/tests/mute_instance.test.ts | 1 - .../alerts_client/tests/unmute_all.test.ts | 1 - .../tests/unmute_instance.test.ts | 1 - .../server/alerts_client/tests/update.test.ts | 54 ++++- .../tests/update_api_key.test.ts | 59 ++++- .../alerts_client_conflict_retries.test.ts | 11 +- .../server/alerts_client_factory.test.ts | 6 +- .../alerts/server/alerts_client_factory.ts | 20 +- x-pack/plugins/alerts/server/config.test.ts | 4 + x-pack/plugins/alerts/server/config.ts | 4 + .../mark_api_key_for_invalidation.test.ts | 47 ++++ .../mark_api_key_for_invalidation.ts | 25 ++ .../invalidate_pending_api_keys/task.ts | 226 ++++++++++++++++++ .../plugins/alerts/server/lib/get_cadence.ts | 53 ++++ x-pack/plugins/alerts/server/plugin.test.ts | 12 + x-pack/plugins/alerts/server/plugin.ts | 14 ++ .../alerts/server/saved_objects/index.ts | 22 ++ x-pack/plugins/alerts/server/types.ts | 12 + .../alerting_api_integration/common/config.ts | 1 + .../plugins/alerts/server/alert_types.ts | 16 ++ .../fixtures/plugins/alerts/server/plugin.ts | 3 + .../fixtures/plugins/alerts/server/routes.ts | 28 +++ .../tests/alerting/update.ts | 74 ++++++ 36 files changed, 847 insertions(+), 126 deletions(-) create mode 100644 x-pack/plugins/alerts/server/invalidate_pending_api_keys/mark_api_key_for_invalidation.test.ts create mode 100644 x-pack/plugins/alerts/server/invalidate_pending_api_keys/mark_api_key_for_invalidation.ts create mode 100644 x-pack/plugins/alerts/server/invalidate_pending_api_keys/task.ts create mode 100644 x-pack/plugins/alerts/server/lib/get_cadence.ts diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index 14bddceb1c03d..e97b37f16faf0 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -31,7 +31,6 @@ import { } from '../types'; import { validateAlertTypeParams, alertExecutionStatusFromRaw } from '../lib'; import { - InvalidateAPIKeyParams, GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult, InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, } from '../../../security/server'; @@ -48,6 +47,7 @@ import { IEvent } from '../../../event_log/server'; import { parseDuration } from '../../common/parse_duration'; import { retryIfConflicts } from '../lib/retry_if_conflicts'; import { partiallyUpdateAlert } from '../saved_objects'; +import { markApiKeyForInvalidation } from '../invalidate_pending_api_keys/mark_api_key_for_invalidation'; export interface RegistryAlertTypeWithAuth extends RegistryAlertType { authorizedConsumers: string[]; @@ -72,7 +72,6 @@ export interface ConstructorOptions { namespace?: string; getUserName: () => Promise; createAPIKey: (name: string) => Promise; - invalidateAPIKey: (params: InvalidateAPIKeyParams) => Promise; getActionsClient: () => Promise; getEventLogClient: () => Promise; kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; @@ -172,9 +171,6 @@ export class AlertsClient { private readonly authorization: AlertsAuthorization; private readonly alertTypeRegistry: AlertTypeRegistry; private readonly createAPIKey: (name: string) => Promise; - private readonly invalidateAPIKey: ( - params: InvalidateAPIKeyParams - ) => Promise; private readonly getActionsClient: () => Promise; private readonly actionsAuthorization: ActionsAuthorization; private readonly getEventLogClient: () => Promise; @@ -191,7 +187,6 @@ export class AlertsClient { namespace, getUserName, createAPIKey, - invalidateAPIKey, encryptedSavedObjectsClient, getActionsClient, actionsAuthorization, @@ -207,7 +202,6 @@ export class AlertsClient { this.unsecuredSavedObjectsClient = unsecuredSavedObjectsClient; this.authorization = authorization; this.createAPIKey = createAPIKey; - this.invalidateAPIKey = invalidateAPIKey; this.encryptedSavedObjectsClient = encryptedSavedObjectsClient; this.getActionsClient = getActionsClient; this.actionsAuthorization = actionsAuthorization; @@ -263,7 +257,11 @@ export class AlertsClient { ); } catch (e) { // Avoid unused API key - this.invalidateApiKey({ apiKey: rawAlert.apiKey }); + markApiKeyForInvalidation( + { apiKey: rawAlert.apiKey }, + this.logger, + this.unsecuredSavedObjectsClient + ); throw e; } if (data.enabled) { @@ -487,7 +485,13 @@ export class AlertsClient { await Promise.all([ taskIdToRemove ? deleteTaskIfItExists(this.taskManager, taskIdToRemove) : null, - apiKeyToInvalidate ? this.invalidateApiKey({ apiKey: apiKeyToInvalidate }) : null, + apiKeyToInvalidate + ? markApiKeyForInvalidation( + { apiKey: apiKeyToInvalidate }, + this.logger, + this.unsecuredSavedObjectsClient + ) + : null, ]); return removeResult; @@ -526,7 +530,11 @@ export class AlertsClient { await Promise.all([ alertSavedObject.attributes.apiKey - ? this.invalidateApiKey({ apiKey: alertSavedObject.attributes.apiKey }) + ? markApiKeyForInvalidation( + { apiKey: alertSavedObject.attributes.apiKey }, + this.logger, + this.unsecuredSavedObjectsClient + ) : null, (async () => { if ( @@ -591,7 +599,11 @@ export class AlertsClient { ); } catch (e) { // Avoid unused API key - this.invalidateApiKey({ apiKey: createAttributes.apiKey }); + markApiKeyForInvalidation( + { apiKey: createAttributes.apiKey }, + this.logger, + this.unsecuredSavedObjectsClient + ); throw e; } @@ -671,28 +683,20 @@ export class AlertsClient { await this.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); } catch (e) { // Avoid unused API key - this.invalidateApiKey({ apiKey: updateAttributes.apiKey }); + markApiKeyForInvalidation( + { apiKey: updateAttributes.apiKey }, + this.logger, + this.unsecuredSavedObjectsClient + ); throw e; } if (apiKeyToInvalidate) { - await this.invalidateApiKey({ apiKey: apiKeyToInvalidate }); - } - } - - private async invalidateApiKey({ apiKey }: { apiKey: string | null }): Promise { - if (!apiKey) { - return; - } - - try { - const apiKeyId = Buffer.from(apiKey, 'base64').toString().split(':')[0]; - const response = await this.invalidateAPIKey({ id: apiKeyId }); - if (response.apiKeysEnabled === true && response.result.error_count > 0) { - this.logger.error(`Failed to invalidate API Key [id="${apiKeyId}"]`); - } - } catch (e) { - this.logger.error(`Failed to invalidate API Key: ${e.message}`); + await markApiKeyForInvalidation( + { apiKey: apiKeyToInvalidate }, + this.logger, + this.unsecuredSavedObjectsClient + ); } } @@ -752,7 +756,11 @@ export class AlertsClient { await this.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); } catch (e) { // Avoid unused API key - this.invalidateApiKey({ apiKey: updateAttributes.apiKey }); + markApiKeyForInvalidation( + { apiKey: updateAttributes.apiKey }, + this.logger, + this.unsecuredSavedObjectsClient + ); throw e; } const scheduledTask = await this.scheduleAlert( @@ -764,7 +772,11 @@ export class AlertsClient { scheduledTaskId: scheduledTask.id, }); if (apiKeyToInvalidate) { - await this.invalidateApiKey({ apiKey: apiKeyToInvalidate }); + await markApiKeyForInvalidation( + { apiKey: apiKeyToInvalidate }, + this.logger, + this.unsecuredSavedObjectsClient + ); } } } @@ -825,7 +837,13 @@ export class AlertsClient { attributes.scheduledTaskId ? deleteTaskIfItExists(this.taskManager, attributes.scheduledTaskId) : null, - apiKeyToInvalidate ? this.invalidateApiKey({ apiKey: apiKeyToInvalidate }) : null, + apiKeyToInvalidate + ? await markApiKeyForInvalidation( + { apiKey: apiKeyToInvalidate }, + this.logger, + this.unsecuredSavedObjectsClient + ) + : null, ]); } } diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/aggregate.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/aggregate.test.ts index 0f89fc6c9c25c..cc5d10c3346e8 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/aggregate.test.ts @@ -34,7 +34,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts index 965ea1949bf3a..ee407b1a6d50c 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts @@ -34,7 +34,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), @@ -711,7 +710,7 @@ describe('create()', () => { expect(taskManager.schedule).not.toHaveBeenCalled(); }); - test('throws error and invalidates API key when create saved object fails', async () => { + test('throws error and add API key to invalidatePendingApiKey SO when create saved object fails', async () => { const data = getMockData(); alertsClientParams.createAPIKey.mockResolvedValueOnce({ apiKeysEnabled: true, @@ -731,11 +730,25 @@ describe('create()', () => { ], }); unsecuredSavedObjectsClient.create.mockRejectedValueOnce(new Error('Test failure')); + const createdAt = new Date().toISOString(); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt, + }, + references: [], + }); await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( `"Test failure"` ); expect(taskManager.schedule).not.toHaveBeenCalled(); - expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '123' }); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(2); + expect(unsecuredSavedObjectsClient.create.mock.calls[1][1]).toStrictEqual({ + apiKeyId: '123', + createdAt, + }); }); test('attempts to remove saved object if scheduling failed', async () => { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts index d9b253c3a56e8..e7b975aec8eb0 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts @@ -32,7 +32,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), @@ -94,11 +93,22 @@ describe('delete()', () => { }); test('successfully removes an alert', async () => { + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); const result = await alertsClient.delete({ id: '1' }); expect(result).toEqual({ success: true }); expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith('alert', '1'); expect(taskManager.remove).toHaveBeenCalledWith('task-123'); - expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '123' }); + expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toBe( + 'api_key_pending_invalidation' + ); expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { namespace: 'default', }); @@ -107,12 +117,21 @@ describe('delete()', () => { test('falls back to SOC.get when getDecryptedAsInternalUser throws an error', async () => { encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValue(new Error('Fail')); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); const result = await alertsClient.delete({ id: '1' }); expect(result).toEqual({ success: true }); expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith('alert', '1'); expect(taskManager.remove).toHaveBeenCalledWith('task-123'); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledWith('alert', '1'); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( 'delete(): Failed to load API key to invalidate on alert 1: Fail' @@ -133,6 +152,15 @@ describe('delete()', () => { }); test(`doesn't invalidate API key when apiKey is null`, async () => { + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({ ...existingAlert, attributes: { @@ -142,24 +170,34 @@ describe('delete()', () => { }); await alertsClient.delete({ id: '1' }); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); test('swallows error when invalidate API key throws', async () => { - alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail')); - + unsecuredSavedObjectsClient.create.mockRejectedValueOnce(new Error('Fail')); await alertsClient.delete({ id: '1' }); - expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '123' }); + expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toBe( + 'api_key_pending_invalidation' + ); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( - 'Failed to invalidate API Key: Fail' + 'Failed to mark for API key [id="MTIzOmFiYw=="] for invalidation: Fail' ); }); test('swallows error when getDecryptedAsInternalUser throws an error', async () => { + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValue(new Error('Fail')); await alertsClient.delete({ id: '1' }); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( 'delete(): Failed to load API key to invalidate on alert 1: Fail' ); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts index d0557df622028..11ce0027f82d8 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts @@ -13,6 +13,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { getBeforeSetup } from './lib'; +import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -33,7 +34,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), @@ -108,6 +108,15 @@ describe('disable()', () => { }); test('disables an alert', async () => { + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); await alertsClient.disable({ id: '1' }); expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { @@ -145,11 +154,22 @@ describe('disable()', () => { } ); expect(taskManager.remove).toHaveBeenCalledWith('task-123'); - expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '123' }); + expect( + (unsecuredSavedObjectsClient.create.mock.calls[0][1] as InvalidatePendingApiKey).apiKeyId + ).toBe('123'); }); test('falls back when getDecryptedAsInternalUser throws an error', async () => { encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValueOnce(new Error('Fail')); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); await alertsClient.disable({ id: '1' }); expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledWith('alert', '1'); @@ -188,7 +208,7 @@ describe('disable()', () => { } ); expect(taskManager.remove).toHaveBeenCalledWith('task-123'); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); test(`doesn't disable already disabled alerts`, async () => { @@ -201,26 +221,54 @@ describe('disable()', () => { }, }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); + await alertsClient.disable({ id: '1' }); expect(unsecuredSavedObjectsClient.update).not.toHaveBeenCalled(); expect(taskManager.remove).not.toHaveBeenCalled(); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); test(`doesn't invalidate when no API key is used`, async () => { + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce(existingAlert); await alertsClient.disable({ id: '1' }); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); test('swallows error when failing to load decrypted saved object', async () => { + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValueOnce(new Error('Fail')); await alertsClient.disable({ id: '1' }); expect(unsecuredSavedObjectsClient.update).toHaveBeenCalled(); expect(taskManager.remove).toHaveBeenCalled(); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( 'disable(): Failed to load API key to invalidate on alert 1: Fail' ); @@ -235,11 +283,10 @@ describe('disable()', () => { }); test('swallows error when invalidate API key throws', async () => { - alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail')); - + unsecuredSavedObjectsClient.create.mockRejectedValueOnce(new Error('Fail')); await alertsClient.disable({ id: '1' }); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( - 'Failed to invalidate API Key: Fail' + 'Failed to mark for API key [id="MTIzOmFiYw=="] for invalidation: Fail' ); }); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts index 215493c71aec7..16e83c42d8930 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts @@ -14,6 +14,7 @@ import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; import { getBeforeSetup } from './lib'; +import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -34,7 +35,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), @@ -147,6 +147,7 @@ describe('enable()', () => { }); test('enables an alert', async () => { + const createdAt = new Date().toISOString(); unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ ...existingAlert, attributes: { @@ -157,13 +158,22 @@ describe('enable()', () => { updatedBy: 'elastic', }, }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt, + }, + references: [], + }); await alertsClient.enable({ id: '1' }); expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { namespace: 'default', }); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).not.toBeCalledWith('api_key_pending_invalidation'); expect(alertsClientParams.createAPIKey).toHaveBeenCalled(); expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith( 'alert', @@ -217,6 +227,7 @@ describe('enable()', () => { }); test('invalidates API key if ever one existed prior to updating', async () => { + const createdAt = new Date().toISOString(); encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({ ...existingAlert, attributes: { @@ -224,13 +235,24 @@ describe('enable()', () => { apiKey: Buffer.from('123:abc').toString('base64'), }, }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt, + }, + references: [], + }); await alertsClient.enable({ id: '1' }); expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { namespace: 'default', }); - expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '123' }); + expect( + (unsecuredSavedObjectsClient.create.mock.calls[0][1] as InvalidatePendingApiKey).apiKeyId + ).toBe('123'); }); test(`doesn't enable already enabled alerts`, async () => { @@ -312,19 +334,31 @@ describe('enable()', () => { }); test('throws error when failing to update the first time', async () => { + const createdAt = new Date().toISOString(); alertsClientParams.createAPIKey.mockResolvedValueOnce({ apiKeysEnabled: true, result: { id: '123', name: '123', api_key: 'abc' }, }); unsecuredSavedObjectsClient.update.mockReset(); unsecuredSavedObjectsClient.update.mockRejectedValueOnce(new Error('Fail to update')); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt, + }, + references: [], + }); await expect(alertsClient.enable({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( `"Fail to update"` ); expect(alertsClientParams.getUserName).toHaveBeenCalled(); expect(alertsClientParams.createAPIKey).toHaveBeenCalled(); - expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '123' }); + expect( + (unsecuredSavedObjectsClient.create.mock.calls[0][1] as InvalidatePendingApiKey).apiKeyId + ).toBe('123'); expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledTimes(1); expect(taskManager.schedule).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts index c1adaddc80d9e..1b3a776bd23e0 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts @@ -35,7 +35,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts index 004230403de2e..5c0d80f159b31 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts @@ -33,7 +33,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index 9cb2a33222d23..269b2eb2ab7a7 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -39,7 +39,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_state.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_state.test.ts index 8b32f05f6d5a1..79a064beba166 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_state.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_state.test.ts @@ -34,7 +34,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/lib.ts b/x-pack/plugins/alerts/server/alerts_client/tests/lib.ts index 5ebb4e90d4b50..028a7c6737474 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/lib.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/lib.ts @@ -46,14 +46,6 @@ export function getBeforeSetup( ) { jest.resetAllMocks(); alertsClientParams.createAPIKey.mockResolvedValue({ apiKeysEnabled: false }); - alertsClientParams.invalidateAPIKey.mockResolvedValue({ - apiKeysEnabled: true, - result: { - invalidated_api_keys: [], - previously_invalidated_api_keys: [], - error_count: 0, - }, - }); alertsClientParams.getUserName.mockResolvedValue('elastic'); taskManager.runNow.mockResolvedValue({ id: '' }); const actionsClient = actionsClientMock.create(); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/list_alert_types.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/list_alert_types.test.ts index b2f5c5498f848..8cbe47655ef68 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/list_alert_types.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/list_alert_types.test.ts @@ -33,7 +33,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts index 88199dfd1f7b9..868fa3d8c6aa2 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts @@ -32,7 +32,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts index cd7112b3551b3..05ca741f480ca 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts @@ -33,7 +33,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts index 07666c1cc6261..5ef1af9b6f0ee 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts @@ -33,7 +33,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts index 97711b8c14579..88692239ac2fe 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts @@ -33,7 +33,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts index 1dcde6addb9bf..ad58e36ade722 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts @@ -10,7 +10,7 @@ import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; -import { IntervalSchedule } from '../../types'; +import { IntervalSchedule, InvalidatePendingApiKey } from '../../types'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; @@ -38,7 +38,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), @@ -161,6 +160,15 @@ describe('update()', () => { }, ], }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '234', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); const result = await alertsClient.update({ id: '1', data: { @@ -241,7 +249,7 @@ describe('update()', () => { namespace: 'default', }); expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); - expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(2); expect(unsecuredSavedObjectsClient.create.mock.calls[0]).toHaveLength(3); expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toEqual('alert'); expect(unsecuredSavedObjectsClient.create.mock.calls[0][1]).toMatchInlineSnapshot(` @@ -376,6 +384,24 @@ describe('update()', () => { }, ], }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '234', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '234', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); const result = await alertsClient.update({ id: '1', data: { @@ -423,7 +449,7 @@ describe('update()', () => { "updatedAt": 2019-02-12T21:01:22.479Z, } `); - expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(2); expect(unsecuredSavedObjectsClient.create.mock.calls[0]).toHaveLength(3); expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toEqual('alert'); expect(unsecuredSavedObjectsClient.create.mock.calls[0][1]).toMatchInlineSnapshot(` @@ -530,6 +556,15 @@ describe('update()', () => { }, ], }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '234', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); const result = await alertsClient.update({ id: '1', data: { @@ -578,7 +613,7 @@ describe('update()', () => { "updatedAt": 2019-02-12T21:01:22.479Z, } `); - expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(2); expect(unsecuredSavedObjectsClient.create.mock.calls[0]).toHaveLength(3); expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toEqual('alert'); expect(unsecuredSavedObjectsClient.create.mock.calls[0][1]).toMatchInlineSnapshot(` @@ -732,7 +767,6 @@ describe('update()', () => { }); it('swallows error when invalidate API key throws', async () => { - alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail')); unsecuredSavedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ { @@ -775,6 +809,7 @@ describe('update()', () => { }, ], }); + unsecuredSavedObjectsClient.create.mockRejectedValueOnce(new Error('Fail')); // add ApiKey to invalidate await alertsClient.update({ id: '1', data: { @@ -797,7 +832,7 @@ describe('update()', () => { }, }); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( - 'Failed to invalidate API Key: Fail' + 'Failed to mark for API key [id="MTIzOmFiYw=="] for invalidation: Fail' ); }); @@ -965,8 +1000,9 @@ describe('update()', () => { }, }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalledWith({ id: '123' }); - expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '234' }); + expect( + (unsecuredSavedObjectsClient.create.mock.calls[1][1] as InvalidatePendingApiKey).apiKeyId + ).toBe('234'); }); describe('updating an alert schedule', () => { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts index 1f3b567b2c031..af178a1fac5f5 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts @@ -13,6 +13,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { getBeforeSetup } from './lib'; +import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -32,7 +33,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger: loggingSystemMock.create().get(), encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), @@ -80,6 +80,15 @@ describe('updateApiKey()', () => { beforeEach(() => { alertsClient = new AlertsClient(alertsClientParams); unsecuredSavedObjectsClient.get.mockResolvedValue(existingAlert); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '234', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingEncryptedAlert); alertsClientParams.createAPIKey.mockResolvedValueOnce({ apiKeysEnabled: true, @@ -121,11 +130,22 @@ describe('updateApiKey()', () => { }, { version: '123' } ); - expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '123' }); + expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toBe( + 'api_key_pending_invalidation' + ); }); test('falls back to SOC when getDecryptedAsInternalUser throws an error', async () => { encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValueOnce(new Error('Fail')); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); await alertsClient.updateApiKey({ id: '1' }); expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledWith('alert', '1'); @@ -160,28 +180,37 @@ describe('updateApiKey()', () => { }, { version: '123' } ); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); test('swallows error when invalidate API key throws', async () => { - alertsClientParams.invalidateAPIKey.mockRejectedValue(new Error('Fail')); + unsecuredSavedObjectsClient.create.mockRejectedValueOnce(new Error('Fail')); await alertsClient.updateApiKey({ id: '1' }); - expect(alertsClientParams.logger.error).toHaveBeenCalledWith( - 'Failed to invalidate API Key: Fail' - ); expect(unsecuredSavedObjectsClient.update).toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toBe( + 'api_key_pending_invalidation' + ); }); test('swallows error when getting decrypted object throws', async () => { encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValueOnce(new Error('Fail')); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '234', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); await alertsClient.updateApiKey({ id: '1' }); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( 'updateApiKey(): Failed to load API key to invalidate on alert 1: Fail' ); expect(unsecuredSavedObjectsClient.update).toHaveBeenCalled(); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); test('throws when unsecuredSavedObjectsClient update fails and invalidates newly created API key', async () => { @@ -190,12 +219,22 @@ describe('updateApiKey()', () => { result: { id: '234', name: '234', api_key: 'abc' }, }); unsecuredSavedObjectsClient.update.mockRejectedValueOnce(new Error('Fail')); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '234', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); await expect(alertsClient.updateApiKey({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( `"Fail"` ); - expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalledWith({ id: '123' }); - expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '234' }); + expect( + (unsecuredSavedObjectsClient.create.mock.calls[0][1] as InvalidatePendingApiKey).apiKeyId + ).toBe('234'); }); describe('authorization', () => { diff --git a/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts b/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts index b1ac5ac4c6783..ca9389ece310c 100644 --- a/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_conflict_retries.test.ts @@ -45,7 +45,6 @@ const alertsClientParams: jest.Mocked = { namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), - invalidateAPIKey: jest.fn(), logger, encryptedSavedObjectsClient: encryptedSavedObjects, getActionsClient: jest.fn(), @@ -115,7 +114,7 @@ async function update(success: boolean) { ); return expectConflict(success, err, 'create'); } - expectSuccess(success, 2, 'create'); + expectSuccess(success, 3, 'create'); // only checking the debug messages in this test expect(logger.debug).nthCalledWith(1, `alertsClient.update('alert-id') conflict, retrying ...`); @@ -306,14 +305,6 @@ beforeEach(() => { jest.resetAllMocks(); alertsClientParams.createAPIKey.mockResolvedValue({ apiKeysEnabled: false }); - alertsClientParams.invalidateAPIKey.mockResolvedValue({ - apiKeysEnabled: true, - result: { - invalidated_api_keys: [], - previously_invalidated_api_keys: [], - error_count: 0, - }, - }); alertsClientParams.getUserName.mockResolvedValue('elastic'); taskManager.runNow.mockResolvedValue({ id: '' }); diff --git a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts index 3cf6666e90eb0..bdbfc726dab8f 100644 --- a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts @@ -92,7 +92,7 @@ test('creates an alerts client with proper constructor arguments when security i expect(savedObjectsService.getScopedClient).toHaveBeenCalledWith(request, { excludedWrappers: ['security'], - includedHiddenTypes: ['alert'], + includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], }); const { AlertsAuthorization } = jest.requireMock('./authorization/alerts_authorization'); @@ -125,7 +125,6 @@ test('creates an alerts client with proper constructor arguments when security i getActionsClient: expect.any(Function), getEventLogClient: expect.any(Function), createAPIKey: expect.any(Function), - invalidateAPIKey: expect.any(Function), encryptedSavedObjectsClient: alertsClientFactoryParams.encryptedSavedObjectsClient, kibanaVersion: '7.10.0', }); @@ -142,7 +141,7 @@ test('creates an alerts client with proper constructor arguments', async () => { expect(savedObjectsService.getScopedClient).toHaveBeenCalledWith(request, { excludedWrappers: ['security'], - includedHiddenTypes: ['alert'], + includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], }); const { AlertsAuthorization } = jest.requireMock('./authorization/alerts_authorization'); @@ -167,7 +166,6 @@ test('creates an alerts client with proper constructor arguments', async () => { namespace: 'default', getUserName: expect.any(Function), createAPIKey: expect.any(Function), - invalidateAPIKey: expect.any(Function), encryptedSavedObjectsClient: alertsClientFactoryParams.encryptedSavedObjectsClient, getActionsClient: expect.any(Function), getEventLogClient: expect.any(Function), diff --git a/x-pack/plugins/alerts/server/alerts_client_factory.ts b/x-pack/plugins/alerts/server/alerts_client_factory.ts index eccd810391307..069703be72f8a 100644 --- a/x-pack/plugins/alerts/server/alerts_client_factory.ts +++ b/x-pack/plugins/alerts/server/alerts_client_factory.ts @@ -14,7 +14,7 @@ import { PluginStartContract as ActionsPluginStartContract } from '../../actions import { AlertsClient } from './alerts_client'; import { ALERTS_FEATURE_ID } from '../common'; import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types'; -import { InvalidateAPIKeyParams, SecurityPluginSetup } from '../../security/server'; +import { SecurityPluginSetup } from '../../security/server'; import { EncryptedSavedObjectsClient } from '../../encrypted_saved_objects/server'; import { TaskManagerStartContract } from '../../task_manager/server'; import { PluginStartContract as FeaturesPluginStart } from '../../features/server'; @@ -94,7 +94,7 @@ export class AlertsClientFactory { alertTypeRegistry: this.alertTypeRegistry, unsecuredSavedObjectsClient: savedObjects.getScopedClient(request, { excludedWrappers: ['security'], - includedHiddenTypes: ['alert'], + includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], }), authorization, actionsAuthorization: actions.getActionsAuthorizationWithRequest(request), @@ -129,22 +129,6 @@ export class AlertsClientFactory { result: createAPIKeyResult, }; }, - async invalidateAPIKey(params: InvalidateAPIKeyParams) { - if (!securityPluginSetup) { - return { apiKeysEnabled: false }; - } - const invalidateAPIKeyResult = await securityPluginSetup.authc.invalidateAPIKeyAsInternalUser( - params - ); - // Null when Elasticsearch security is disabled - if (!invalidateAPIKeyResult) { - return { apiKeysEnabled: false }; - } - return { - apiKeysEnabled: true, - result: invalidateAPIKeyResult, - }; - }, async getActionsClient() { return actions.getActionsClientWithRequest(request); }, diff --git a/x-pack/plugins/alerts/server/config.test.ts b/x-pack/plugins/alerts/server/config.test.ts index 93aa3c38a0460..bf3b30b5d2378 100644 --- a/x-pack/plugins/alerts/server/config.test.ts +++ b/x-pack/plugins/alerts/server/config.test.ts @@ -13,6 +13,10 @@ describe('config validation', () => { "healthCheck": Object { "interval": "60m", }, + "invalidateApiKeysTask": Object { + "interval": "5m", + "removalDelay": "5m", + }, } `); }); diff --git a/x-pack/plugins/alerts/server/config.ts b/x-pack/plugins/alerts/server/config.ts index a6d2196a407b5..41340c7dfe5fc 100644 --- a/x-pack/plugins/alerts/server/config.ts +++ b/x-pack/plugins/alerts/server/config.ts @@ -11,6 +11,10 @@ export const configSchema = schema.object({ healthCheck: schema.object({ interval: schema.string({ validate: validateDurationSchema, defaultValue: '60m' }), }), + invalidateApiKeysTask: schema.object({ + interval: schema.string({ validate: validateDurationSchema, defaultValue: '5m' }), + removalDelay: schema.string({ validate: validateDurationSchema, defaultValue: '5m' }), + }), }); export type AlertsConfig = TypeOf; diff --git a/x-pack/plugins/alerts/server/invalidate_pending_api_keys/mark_api_key_for_invalidation.test.ts b/x-pack/plugins/alerts/server/invalidate_pending_api_keys/mark_api_key_for_invalidation.test.ts new file mode 100644 index 0000000000000..7b30c22c47f8a --- /dev/null +++ b/x-pack/plugins/alerts/server/invalidate_pending_api_keys/mark_api_key_for_invalidation.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { loggingSystemMock, savedObjectsClientMock } from '../../../../../src/core/server/mocks'; +import { markApiKeyForInvalidation } from './mark_api_key_for_invalidation'; + +describe('markApiKeyForInvalidation', () => { + test('should call savedObjectsClient create with the proper params', async () => { + const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt: '2019-02-12T21:01:22.479Z', + }, + references: [], + }); + await markApiKeyForInvalidation( + { apiKey: Buffer.from('123:abc').toString('base64') }, + loggingSystemMock.create().get(), + unsecuredSavedObjectsClient + ); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.create.mock.calls[0]).toHaveLength(2); + expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toEqual( + 'api_key_pending_invalidation' + ); + }); + + test('should log the proper error when savedObjectsClient create failed', async () => { + const logger = loggingSystemMock.create().get(); + const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); + unsecuredSavedObjectsClient.create.mockRejectedValueOnce(new Error('Fail')); + await markApiKeyForInvalidation( + { apiKey: Buffer.from('123').toString('base64') }, + logger, + unsecuredSavedObjectsClient + ); + expect(logger.error).toHaveBeenCalledWith( + 'Failed to mark for API key [id="MTIz"] for invalidation: Fail' + ); + }); +}); diff --git a/x-pack/plugins/alerts/server/invalidate_pending_api_keys/mark_api_key_for_invalidation.ts b/x-pack/plugins/alerts/server/invalidate_pending_api_keys/mark_api_key_for_invalidation.ts new file mode 100644 index 0000000000000..db25f5b3e19eb --- /dev/null +++ b/x-pack/plugins/alerts/server/invalidate_pending_api_keys/mark_api_key_for_invalidation.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 { Logger, SavedObjectsClientContract } from 'src/core/server'; + +export const markApiKeyForInvalidation = async ( + { apiKey }: { apiKey: string | null }, + logger: Logger, + savedObjectsClient: SavedObjectsClientContract +): Promise => { + if (!apiKey) { + return; + } + try { + const apiKeyId = Buffer.from(apiKey, 'base64').toString().split(':')[0]; + await savedObjectsClient.create('api_key_pending_invalidation', { + apiKeyId, + createdAt: new Date().toISOString(), + }); + } catch (e) { + logger.error(`Failed to mark for API key [id="${apiKey}"] for invalidation: ${e.message}`); + } +}; diff --git a/x-pack/plugins/alerts/server/invalidate_pending_api_keys/task.ts b/x-pack/plugins/alerts/server/invalidate_pending_api_keys/task.ts new file mode 100644 index 0000000000000..77cbb9f4a4a85 --- /dev/null +++ b/x-pack/plugins/alerts/server/invalidate_pending_api_keys/task.ts @@ -0,0 +1,226 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Logger, + CoreStart, + SavedObjectsFindResponse, + KibanaRequest, + SavedObjectsClientContract, +} from 'kibana/server'; +import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; +import { InvalidateAPIKeyParams, SecurityPluginSetup } from '../../../security/server'; +import { + RunContext, + TaskManagerSetupContract, + TaskManagerStartContract, +} from '../../../task_manager/server'; +import { InvalidateAPIKeyResult } from '../alerts_client'; +import { AlertsConfig } from '../config'; +import { timePeriodBeforeDate } from '../lib/get_cadence'; +import { AlertingPluginsStart } from '../plugin'; +import { InvalidatePendingApiKey } from '../types'; + +const TASK_TYPE = 'alerts_invalidate_api_keys'; +export const TASK_ID = `Alerts-${TASK_TYPE}`; + +const invalidateAPIKey = async ( + params: InvalidateAPIKeyParams, + securityPluginSetup?: SecurityPluginSetup +): Promise => { + if (!securityPluginSetup) { + return { apiKeysEnabled: false }; + } + const invalidateAPIKeyResult = await securityPluginSetup.authc.invalidateAPIKeyAsInternalUser( + params + ); + // Null when Elasticsearch security is disabled + if (!invalidateAPIKeyResult) { + return { apiKeysEnabled: false }; + } + return { + apiKeysEnabled: true, + result: invalidateAPIKeyResult, + }; +}; + +export function initializeApiKeyInvalidator( + logger: Logger, + coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]>, + taskManager: TaskManagerSetupContract, + config: Promise, + securityPluginSetup?: SecurityPluginSetup +) { + registerApiKeyInvalitorTaskDefinition( + logger, + coreStartServices, + taskManager, + config, + securityPluginSetup + ); +} + +export async function scheduleApiKeyInvalidatorTask( + logger: Logger, + config: Promise, + taskManager: TaskManagerStartContract +) { + const interval = (await config).invalidateApiKeysTask.interval; + try { + await taskManager.ensureScheduled({ + id: TASK_ID, + taskType: TASK_TYPE, + schedule: { + interval, + }, + state: {}, + params: {}, + }); + } catch (e) { + logger.debug(`Error scheduling task, received ${e.message}`); + } +} + +function registerApiKeyInvalitorTaskDefinition( + logger: Logger, + coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]>, + taskManager: TaskManagerSetupContract, + config: Promise, + securityPluginSetup?: SecurityPluginSetup +) { + taskManager.registerTaskDefinitions({ + [TASK_TYPE]: { + title: 'Invalidate alert API Keys', + createTaskRunner: taskRunner(logger, coreStartServices, config, securityPluginSetup), + }, + }); +} + +function getFakeKibanaRequest(basePath: string) { + const requestHeaders: Record = {}; + return ({ + headers: requestHeaders, + getBasePath: () => basePath, + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + } as unknown) as KibanaRequest; +} + +function taskRunner( + logger: Logger, + coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]>, + config: Promise, + securityPluginSetup?: SecurityPluginSetup +) { + return ({ taskInstance }: RunContext) => { + const { state } = taskInstance; + return { + async run() { + let totalInvalidated = 0; + const configResult = await config; + try { + const [{ savedObjects, http }, { encryptedSavedObjects }] = await coreStartServices; + const savedObjectsClient = savedObjects.getScopedClient( + getFakeKibanaRequest(http.basePath.serverBasePath), + { + includedHiddenTypes: ['api_key_pending_invalidation'], + excludedWrappers: ['security'], + } + ); + const encryptedSavedObjectsClient = encryptedSavedObjects.getClient({ + includedHiddenTypes: ['api_key_pending_invalidation'], + }); + const configuredDelay = configResult.invalidateApiKeysTask.removalDelay; + const delay = timePeriodBeforeDate(new Date(), configuredDelay).toISOString(); + + let hasApiKeysPendingInvalidation = true; + const PAGE_SIZE = 100; + do { + const apiKeysToInvalidate = await savedObjectsClient.find({ + type: 'api_key_pending_invalidation', + filter: `api_key_pending_invalidation.attributes.createdAt <= "${delay}"`, + page: 1, + sortField: 'createdAt', + sortOrder: 'asc', + perPage: PAGE_SIZE, + }); + totalInvalidated += await invalidateApiKeys( + logger, + savedObjectsClient, + apiKeysToInvalidate, + encryptedSavedObjectsClient, + securityPluginSetup + ); + + hasApiKeysPendingInvalidation = apiKeysToInvalidate.total > PAGE_SIZE; + } while (hasApiKeysPendingInvalidation); + + return { + state: { + runs: (state.runs || 0) + 1, + total_invalidated: totalInvalidated, + }, + schedule: { + interval: configResult.invalidateApiKeysTask.interval, + }, + }; + } catch (e) { + logger.warn(`Error executing alerting apiKey invalidation task: ${e.message}`); + return { + state: { + runs: (state.runs || 0) + 1, + total_invalidated: totalInvalidated, + }, + schedule: { + interval: configResult.invalidateApiKeysTask.interval, + }, + }; + } + }, + }; + }; +} + +async function invalidateApiKeys( + logger: Logger, + savedObjectsClient: SavedObjectsClientContract, + apiKeysToInvalidate: SavedObjectsFindResponse, + encryptedSavedObjectsClient: EncryptedSavedObjectsClient, + securityPluginSetup?: SecurityPluginSetup +) { + let totalInvalidated = 0; + await Promise.all( + apiKeysToInvalidate.saved_objects.map(async (apiKeyObj) => { + const decryptedApiKey = await encryptedSavedObjectsClient.getDecryptedAsInternalUser< + InvalidatePendingApiKey + >('api_key_pending_invalidation', apiKeyObj.id); + const apiKeyId = decryptedApiKey.attributes.apiKeyId; + const response = await invalidateAPIKey({ id: apiKeyId }, securityPluginSetup); + if (response.apiKeysEnabled === true && response.result.error_count > 0) { + logger.error(`Failed to invalidate API Key [id="${apiKeyObj.attributes.apiKeyId}"]`); + } else { + try { + await savedObjectsClient.delete('api_key_pending_invalidation', apiKeyObj.id); + totalInvalidated++; + } catch (err) { + logger.error( + `Failed to cleanup api key "${apiKeyObj.attributes.apiKeyId}". Error: ${err.message}` + ); + } + } + }) + ); + logger.debug(`Total invalidated api keys "${totalInvalidated}"`); + return totalInvalidated; +} diff --git a/x-pack/plugins/alerts/server/lib/get_cadence.ts b/x-pack/plugins/alerts/server/lib/get_cadence.ts new file mode 100644 index 0000000000000..d09ed0c2122cd --- /dev/null +++ b/x-pack/plugins/alerts/server/lib/get_cadence.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { memoize } from 'lodash'; + +export enum TimeUnit { + Minute = 'm', + Second = 's', + Hour = 'h', + Day = 'd', +} +const VALID_CADENCE = new Set(Object.values(TimeUnit)); +const CADENCE_IN_MS: Record = { + [TimeUnit.Second]: 1000, + [TimeUnit.Minute]: 60 * 1000, + [TimeUnit.Hour]: 60 * 60 * 1000, + [TimeUnit.Day]: 24 * 60 * 60 * 1000, +}; + +const isNumeric = (numAsStr: string) => /^\d+$/.test(numAsStr); + +export const parseIntervalAsMillisecond = memoize((value: string): number => { + const numericAsStr: string = value.slice(0, -1); + const numeric: number = parseInt(numericAsStr, 10); + const cadence: TimeUnit | string = value.slice(-1); + if ( + !VALID_CADENCE.has(cadence as TimeUnit) || + isNaN(numeric) || + numeric <= 0 || + !isNumeric(numericAsStr) + ) { + throw new Error( + `Invalid time value "${value}". Time must be of the form {number}m. Example: 5m.` + ); + } + return numeric * CADENCE_IN_MS[cadence as TimeUnit]; +}); + +/** + * Returns a date that is the specified interval from given date. + * + * @param {Date} date - The date to add interval to + * @param {string} interval - THe time of the form `Nm` such as `5m` + */ +export function timePeriodBeforeDate(date: Date, timePeriod: string): Date { + const result = new Date(date.valueOf()); + const milisecFromTime = parseIntervalAsMillisecond(timePeriod); + result.setMilliseconds(result.getMilliseconds() - milisecFromTime); + return result; +} diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts index 715fbc6aeed45..62f4b7d5a3fc4 100644 --- a/x-pack/plugins/alerts/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -22,6 +22,10 @@ describe('Alerting Plugin', () => { healthCheck: { interval: '5m', }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '5m', + }, }); const plugin = new AlertingPlugin(context); @@ -67,6 +71,10 @@ describe('Alerting Plugin', () => { healthCheck: { interval: '5m', }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '5m', + }, }); const plugin = new AlertingPlugin(context); @@ -114,6 +122,10 @@ describe('Alerting Plugin', () => { healthCheck: { interval: '5m', }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '5m', + }, }); const plugin = new AlertingPlugin(context); diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts index 1fa89606a76fc..0c91e93938346 100644 --- a/x-pack/plugins/alerts/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -65,6 +65,10 @@ import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/ import { IEventLogger, IEventLogService, IEventLogClientService } from '../../event_log/server'; import { PluginStartContract as FeaturesPluginStart } from '../../features/server'; import { setupSavedObjects } from './saved_objects'; +import { + initializeApiKeyInvalidator, + scheduleApiKeyInvalidatorTask, +} from './invalidate_pending_api_keys/task'; import { getHealthStatusStream, scheduleAlertingHealthCheck, @@ -200,6 +204,14 @@ export class AlertingPlugin { }); } + initializeApiKeyInvalidator( + this.logger, + core.getStartServices(), + plugins.taskManager, + this.config, + this.security + ); + core.getStartServices().then(async ([, startPlugins]) => { core.status.set( combineLatest([ @@ -308,7 +320,9 @@ export class AlertingPlugin { }); scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager); + scheduleAlertingHealthCheck(this.logger, this.config, plugins.taskManager); + scheduleApiKeyInvalidatorTask(this.telemetryLogger, this.config, plugins.taskManager); return { listTypes: alertTypeRegistry!.list.bind(this.alertTypeRegistry!), diff --git a/x-pack/plugins/alerts/server/saved_objects/index.ts b/x-pack/plugins/alerts/server/saved_objects/index.ts index 9aa1f86676eaa..da30273e93c6b 100644 --- a/x-pack/plugins/alerts/server/saved_objects/index.ts +++ b/x-pack/plugins/alerts/server/saved_objects/index.ts @@ -42,10 +42,32 @@ export function setupSavedObjects( mappings: mappings.alert, }); + savedObjects.registerType({ + name: 'api_key_pending_invalidation', + hidden: true, + namespaceType: 'agnostic', + mappings: { + properties: { + apiKeyId: { + type: 'keyword', + }, + createdAt: { + type: 'date', + }, + }, + }, + }); + // Encrypted attributes encryptedSavedObjects.registerType({ type: 'alert', attributesToEncrypt: new Set(['apiKey']), attributesToExcludeFromAAD: new Set(AlertAttributesExcludedFromAAD), }); + + // Encrypted attributes + encryptedSavedObjects.registerType({ + type: 'api_key_pending_invalidation', + attributesToEncrypt: new Set(['apiKeyId']), + }); } diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 9226461f6e30a..dde1628156658 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -180,4 +180,16 @@ export interface AlertsConfigType { }; } +export interface AlertsConfigType { + invalidateApiKeysTask: { + interval: string; + removalDelay: string; + }; +} + +export interface InvalidatePendingApiKey { + apiKeyId: string; + createdAt: string; +} + export type AlertTypeRegistry = PublicMethodsOf; diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index f9fdfaed1c79b..cb78e76bdd697 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -92,6 +92,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ...xPackApiIntegrationTestsConfig.get('kbnTestServer.serverArgs'), `--xpack.actions.allowedHosts=${JSON.stringify(['localhost', 'some.non.existent.com'])}`, '--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', + '--xpack.alerts.invalidateApiKeysTask.interval="15s"', `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, ...actionsProxyUrl, diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts index 7ed864afac4cc..998ec6ab2ed0e 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts @@ -437,6 +437,21 @@ export function defineAlertTypes( throw new Error('this alert is intended to fail'); }, }; + const longRunningAlertType: AlertType = { + id: 'test.longRunning', + name: 'Test: Long Running', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + producer: 'alertsFixture', + defaultActionGroupId: 'default', + async executor() { + await new Promise((resolve) => setTimeout(resolve, 5000)); + }, + }; alerts.registerType(getAlwaysFiringAlertType()); alerts.registerType(getCumulativeFiringAlertType()); @@ -449,4 +464,5 @@ export function defineAlertTypes( alerts.registerType(onlyStateVariablesAlertType); alerts.registerType(getPatternFiringAlertType()); alerts.registerType(throwAlertType); + alerts.registerType(longRunningAlertType); } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts index fbf3b798500d3..d832902fe066d 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts @@ -50,6 +50,7 @@ export class FixturePlugin implements Plugin, + res: KibanaResponseFactory + ): Promise> => { + try { + const [{ savedObjects }] = await core.getStartServices(); + const savedObjectsWithTasksAndAlerts = await savedObjects.getScopedClient(req, { + includedHiddenTypes: ['api_key_pending_invalidation'], + }); + const findResult = await savedObjectsWithTasksAndAlerts.find({ + type: 'api_key_pending_invalidation', + }); + return res.ok({ + body: { apiKeysToInvalidate: findResult.saved_objects }, + }); + } catch (err) { + return res.badRequest({ body: err }); + } + } + ); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index 8836bc2e4db2f..9c3d2801c0886 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -836,6 +836,80 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { } }); + it('should handle updates for a long running alert type without failing the underlying tasks due to invalidated ApiKey', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .set('kbn-xsrf', 'foo') + .send({ + enabled: true, + name: 'abc', + tags: ['foo'], + alertTypeId: 'test.longRunning', + consumer: 'alertsFixture', + schedule: { interval: '1s' }, + throttle: '1m', + actions: [], + params: {}, + }) + .expect(200); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + const updatedData = { + name: 'bcd', + tags: ['bar'], + params: { + foo: true, + }, + schedule: { interval: '1m' }, + actions: [], + throttle: '1m', + }; + const response = await supertestWithoutAuth + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .send(updatedData); + + const statusUpdates: string[] = []; + await retry.try(async () => { + const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; + statusUpdates.push(alertTask.status); + expect(alertTask.status).to.eql('idle'); + }); + + expect(statusUpdates.find((status) => status === 'failed')).to.be(undefined); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + expect(response.statusCode).to.eql(403); + expect(response.body).to.eql({ + error: 'Forbidden', + message: getConsumerUnauthorizedErrorMessage( + 'update', + 'test.longRunning', + 'alertsFixture' + ), + statusCode: 403, + }); + break; + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.statusCode).to.eql(200); + await retry.try(async () => { + const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; + expect(alertTask.status).to.eql('idle'); + // ensure the alert is rescheduled to a minute from now + ensureDatetimeIsWithinRange(Date.parse(alertTask.runAt), 60 * 1000); + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + it('should handle updates to an alert schedule by setting the new schedule for the underlying task', async () => { const { body: createdAlert } = await supertest .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) From d0441e1fee646f8dbb1fcddb07105023402a36dd Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Tue, 17 Nov 2020 09:19:46 -0600 Subject: [PATCH 10/10] Move src/legacy/server/keystore to src/cli (#83483) Currently keystore parsing is in the legacy http server folder. Keystore references end in src/cli so instead of migrating to core/server the I opted for the closest fit in the CLI folder. Closes #52107 --- .github/CODEOWNERS | 2 +- src/{legacy/server => cli}/keystore/errors.js | 0 src/{legacy/server => cli}/keystore/index.js | 0 src/{legacy/server => cli}/keystore/keystore.js | 0 src/{legacy/server => cli}/keystore/keystore.test.js | 0 src/cli/{serve => keystore}/read_keystore.js | 2 +- src/cli/{serve => keystore}/read_keystore.test.js | 4 ++-- src/cli/serve/serve.js | 2 +- src/cli_keystore/add.test.js | 2 +- src/cli_keystore/cli_keystore.js | 2 +- src/cli_keystore/create.test.js | 2 +- src/cli_keystore/list.test.js | 2 +- src/cli_keystore/remove.test.js | 2 +- 13 files changed, 10 insertions(+), 10 deletions(-) rename src/{legacy/server => cli}/keystore/errors.js (100%) rename src/{legacy/server => cli}/keystore/index.js (100%) rename src/{legacy/server => cli}/keystore/keystore.js (100%) rename src/{legacy/server => cli}/keystore/keystore.test.js (100%) rename src/cli/{serve => keystore}/read_keystore.js (95%) rename src/cli/{serve => keystore}/read_keystore.test.js (94%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b7fb3ff04db71..af010089e4892 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -158,7 +158,7 @@ /packages/kbn-ui-shared-deps/ @elastic/kibana-operations /packages/kbn-es-archiver/ @elastic/kibana-operations /packages/kbn-utils/ @elastic/kibana-operations -/src/legacy/server/keystore/ @elastic/kibana-operations +/src/cli/keystore/ @elastic/kibana-operations /src/legacy/server/warnings/ @elastic/kibana-operations /.ci/es-snapshots/ @elastic/kibana-operations /vars/ @elastic/kibana-operations diff --git a/src/legacy/server/keystore/errors.js b/src/cli/keystore/errors.js similarity index 100% rename from src/legacy/server/keystore/errors.js rename to src/cli/keystore/errors.js diff --git a/src/legacy/server/keystore/index.js b/src/cli/keystore/index.js similarity index 100% rename from src/legacy/server/keystore/index.js rename to src/cli/keystore/index.js diff --git a/src/legacy/server/keystore/keystore.js b/src/cli/keystore/keystore.js similarity index 100% rename from src/legacy/server/keystore/keystore.js rename to src/cli/keystore/keystore.js diff --git a/src/legacy/server/keystore/keystore.test.js b/src/cli/keystore/keystore.test.js similarity index 100% rename from src/legacy/server/keystore/keystore.test.js rename to src/cli/keystore/keystore.test.js diff --git a/src/cli/serve/read_keystore.js b/src/cli/keystore/read_keystore.js similarity index 95% rename from src/cli/serve/read_keystore.js rename to src/cli/keystore/read_keystore.js index 38d0e68bd5c4e..b3bca4cf11c39 100644 --- a/src/cli/serve/read_keystore.js +++ b/src/cli/keystore/read_keystore.js @@ -19,7 +19,7 @@ import { set } from '@elastic/safer-lodash-set'; -import { Keystore } from '../../legacy/server/keystore'; +import { Keystore } from '../keystore'; import { getKeystore } from '../../cli_keystore/get_keystore'; export function readKeystore(keystorePath = getKeystore()) { diff --git a/src/cli/serve/read_keystore.test.js b/src/cli/keystore/read_keystore.test.js similarity index 94% rename from src/cli/serve/read_keystore.test.js rename to src/cli/keystore/read_keystore.test.js index e5407b257a909..a35258febfb8e 100644 --- a/src/cli/serve/read_keystore.test.js +++ b/src/cli/keystore/read_keystore.test.js @@ -20,8 +20,8 @@ import path from 'path'; import { readKeystore } from './read_keystore'; -jest.mock('../../legacy/server/keystore'); -import { Keystore } from '../../legacy/server/keystore'; +jest.mock('../keystore'); +import { Keystore } from '../keystore'; describe('cli/serve/read_keystore', () => { beforeEach(() => { diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index a1715cf3dba2c..f344d3b70ed9d 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -27,7 +27,7 @@ import { getConfigPath } from '@kbn/utils'; import { IS_KIBANA_DISTRIBUTABLE } from '../../legacy/utils'; import { fromRoot } from '../../core/server/utils'; import { bootstrap } from '../../core/server'; -import { readKeystore } from './read_keystore'; +import { readKeystore } from '../keystore/read_keystore'; function canRequire(path) { try { diff --git a/src/cli_keystore/add.test.js b/src/cli_keystore/add.test.js index ba381ca2f3e14..74a72fe44d398 100644 --- a/src/cli_keystore/add.test.js +++ b/src/cli_keystore/add.test.js @@ -39,7 +39,7 @@ jest.mock('fs', () => ({ import sinon from 'sinon'; import { PassThrough } from 'stream'; -import { Keystore } from '../legacy/server/keystore'; +import { Keystore } from '../cli/keystore'; import { add } from './add'; import { Logger } from '../cli_plugin/lib/logger'; import * as prompt from './utils/prompt'; diff --git a/src/cli_keystore/cli_keystore.js b/src/cli_keystore/cli_keystore.js index d12c80b361c92..9fbea8f195122 100644 --- a/src/cli_keystore/cli_keystore.js +++ b/src/cli_keystore/cli_keystore.js @@ -21,7 +21,7 @@ import _ from 'lodash'; import { pkg } from '../core/server/utils'; import Command from '../cli/command'; -import { Keystore } from '../legacy/server/keystore'; +import { Keystore } from '../cli/keystore'; import { createCli } from './create'; import { listCli } from './list'; diff --git a/src/cli_keystore/create.test.js b/src/cli_keystore/create.test.js index cb85475eab1cb..346fa9e055129 100644 --- a/src/cli_keystore/create.test.js +++ b/src/cli_keystore/create.test.js @@ -38,7 +38,7 @@ jest.mock('fs', () => ({ import sinon from 'sinon'; -import { Keystore } from '../legacy/server/keystore'; +import { Keystore } from '../cli/keystore'; import { create } from './create'; import { Logger } from '../cli_plugin/lib/logger'; import * as prompt from './utils/prompt'; diff --git a/src/cli_keystore/list.test.js b/src/cli_keystore/list.test.js index 11c474f908216..8da235a1932e6 100644 --- a/src/cli_keystore/list.test.js +++ b/src/cli_keystore/list.test.js @@ -36,7 +36,7 @@ jest.mock('fs', () => ({ })); import sinon from 'sinon'; -import { Keystore } from '../legacy/server/keystore'; +import { Keystore } from '../cli//keystore'; import { list } from './list'; import { Logger } from '../cli_plugin/lib/logger'; diff --git a/src/cli_keystore/remove.test.js b/src/cli_keystore/remove.test.js index fae8924c67287..fb700e6a8b9e2 100644 --- a/src/cli_keystore/remove.test.js +++ b/src/cli_keystore/remove.test.js @@ -30,7 +30,7 @@ jest.mock('fs', () => ({ import sinon from 'sinon'; -import { Keystore } from '../legacy/server/keystore'; +import { Keystore } from '../cli/keystore'; import { remove } from './remove'; describe('Kibana keystore', () => {