From 91ed1dd87ca5008efc92699ba74c3bbea57aa099 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Fri, 15 May 2020 22:05:54 +0200 Subject: [PATCH 01/18] allow any type for customResponseHeaders config (#66689) * allow any type of value for customResponseHeaders and convert them * fix test config creation * add `?? {}` to avoid breaking all tests... --- .../http/cookie_session_storage.test.ts | 1 + src/core/server/http/http_config.test.ts | 47 ++++++++++++++++++- src/core/server/http/http_config.ts | 18 +++++-- src/core/server/http/test_utils.ts | 1 + 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index 4ce422e1f65c4..0ca87eae6e235 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -62,6 +62,7 @@ configService.atPath.mockReturnValue( disableProtection: true, whitelist: [], }, + customResponseHeaders: {}, } as any) ); diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 7ac707b0f3d83..0976bfd56682e 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -18,7 +18,8 @@ */ import uuid from 'uuid'; -import { config } from '.'; +import { config, HttpConfig } from './http_config'; +import { CspConfig } from '../csp'; const validHostnames = ['www.example.com', '8.8.8.8', '::1', 'localhost']; const invalidHostname = 'asdf$%^'; @@ -107,6 +108,23 @@ test('throws if xsrf.whitelist element does not start with a slash', () => { ); }); +test('accepts any type of objects for custom headers', () => { + const httpSchema = config.schema; + const obj = { + customResponseHeaders: { + string: 'string', + bool: true, + number: 12, + array: [1, 2, 3], + nested: { + foo: 1, + bar: 'dolly', + }, + }, + }; + expect(() => httpSchema.validate(obj)).not.toThrow(); +}); + describe('with TLS', () => { test('throws if TLS is enabled but `redirectHttpFromPort` is equal to `port`', () => { const httpSchema = config.schema; @@ -173,3 +191,30 @@ describe('with compression', () => { expect(() => httpSchema.validate(obj)).toThrowErrorMatchingSnapshot(); }); }); + +describe('HttpConfig', () => { + it('converts customResponseHeaders to strings or arrays of strings', () => { + const httpSchema = config.schema; + const rawConfig = httpSchema.validate({ + customResponseHeaders: { + string: 'string', + bool: true, + number: 12, + array: [1, 2, 3], + nested: { + foo: 1, + bar: 'dolly', + }, + }, + }); + const httpConfig = new HttpConfig(rawConfig, CspConfig.DEFAULT); + + expect(httpConfig.customResponseHeaders).toEqual({ + string: 'string', + bool: 'true', + number: '12', + array: ['1', '2', '3'], + nested: '{"foo":1,"bar":"dolly"}', + }); + }); +}); diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 73f44f3c5ab5c..7c72e3270743e 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -57,7 +57,7 @@ export const config = { ), schema.boolean({ defaultValue: false }) ), - customResponseHeaders: schema.recordOf(schema.string(), schema.string(), { + customResponseHeaders: schema.recordOf(schema.string(), schema.any(), { defaultValue: {}, }), host: schema.string({ @@ -136,7 +136,7 @@ export class HttpConfig { public socketTimeout: number; public port: number; public cors: boolean | { origin: string[] }; - public customResponseHeaders: Record; + public customResponseHeaders: Record; public maxPayload: ByteSizeValue; public basePath?: string; public rewriteBasePath: boolean; @@ -153,7 +153,15 @@ export class HttpConfig { this.host = rawHttpConfig.host; this.port = rawHttpConfig.port; this.cors = rawHttpConfig.cors; - this.customResponseHeaders = rawHttpConfig.customResponseHeaders; + this.customResponseHeaders = Object.entries(rawHttpConfig.customResponseHeaders ?? {}).reduce( + (headers, [key, value]) => { + return { + ...headers, + [key]: Array.isArray(value) ? value.map(e => convertHeader(e)) : convertHeader(value), + }; + }, + {} + ); this.maxPayload = rawHttpConfig.maxPayload; this.name = rawHttpConfig.name; this.basePath = rawHttpConfig.basePath; @@ -166,3 +174,7 @@ export class HttpConfig { this.xsrf = rawHttpConfig.xsrf; } } + +const convertHeader = (entry: any): string => { + return typeof entry === 'object' ? JSON.stringify(entry) : String(entry); +}; diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index 49c4d690c6876..0e639aa72a825 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -45,6 +45,7 @@ configService.atPath.mockReturnValue( disableProtection: true, whitelist: [], }, + customResponseHeaders: {}, } as any) ); From ab2600f823f04740ef3c0a9de4312eea5fd84236 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 15 May 2020 14:17:18 -0600 Subject: [PATCH 02/18] [SIEM][Lists] Adds 90% of the REST API and client API for exception lists and exception items ## Summary See for more details: https://github.com/elastic/kibana/issues/65938 Adds pieces of the `exception list` and `exception list item` and refactors/cleans the code up where I had parts incorrect with little things such as the javascript library io-ts. Some unit tests were added but I am holding off until more of the operations solidify before adding the unit tests. Everything is still behind a feature flag that must be enabled and not advised still at this point to use so I feel ok pushing these parts forward. Adds to the API: - Create exception list - Read exception list - Update exception list - Delete exception list (and exception list items that are associated with it) - Create exception list item - Find exception list (/_find) - Read exception list item - Update exception list item - Delete exception list items individually - Find exception list item (/_find) What is still missing from the REST and client API? - Patch exception list - Patch exception list item - Bulk versions of everything - Import/Export options for these exception lists and list items ### Manual testing and REST API endpoints Go here: ```sh /projects/kibana/x-pack/plugins/lists/server/scripts ``` See the files: ```sh delete_all_exception_lists.sh delete_exception_list.sh delete_exception_list_by_id.sh delete_exception_list_item.sh delete_exception_list_item_by_id.sh exception_lists find_exception_list_items.sh find_exception_lists.sh get_exception_list.sh get_exception_list_by_id.sh get_exception_list_item.sh get_exception_list_item_by_id.sh post_exception_list.sh post_exception_list_item.sh update_exception_list.sh update_exception_list_item.sh ``` Ensure you first run: ```sh ./hard_reset ``` and ensure you have setup your kibana.dev.yml to have: ```yml # Enable lists feature xpack.lists.enabled: true xpack.lists.listIndex: '.lists-frank' xpack.lists.listItemIndex: '.items-frank' ``` Then you can use the above scripts to create, read, update, and delete exception list and exception list items as well as perform find commands against them all. ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios Note: Some but limited unit tests at this point. --- x-pack/plugins/lists/common/constants.ts | 10 +- .../lists/common/schemas/common/schemas.ts | 46 +++- .../elastic_query/update_es_list_schema.ts | 1 + x-pack/plugins/lists/common/schemas/index.ts | 6 +- .../create_exception_list_item_schema.ts | 65 +++++ .../create_exception_list_schema.mock.ts | 19 ++ .../create_exception_list_schema.test.ts | 96 +++++++ .../request/create_exception_list_schema.ts | 55 ++++ .../request/create_list_item_schema.ts | 4 +- .../schemas/request/create_list_schema.ts | 4 +- .../delete_exception_list_item_schema.ts | 20 ++ .../request/delete_exception_list_schema.ts | 20 ++ .../request/delete_list_item_schema.ts | 4 +- .../find_exception_list_item_schema.ts | 35 +++ .../request/find_exception_list_schema.ts | 28 ++ .../request/import_list_item_query_schema.ts | 6 +- .../lists/common/schemas/request/index.ts | 11 + .../schemas/request/patch_list_item_schema.ts | 4 +- .../schemas/request/patch_list_schema.ts | 6 +- .../read_exception_list_item_schema.ts | 20 ++ .../request/read_exception_list_schema.ts | 20 ++ .../schemas/request/read_list_item_schema.ts | 6 +- .../update_exception_list_item_schema.ts | 63 +++++ .../request/update_exception_list_schema.ts | 53 ++++ .../request/update_list_item_schema.ts | 8 +- .../schemas/request/update_list_schema.ts | 8 +- .../response/exception_list_item_schema.ts | 52 ++++ .../schemas/response/exception_list_schema.ts | 46 ++++ .../found_exception_list_item_schema.ts | 24 ++ .../response/found_exception_list_schema.ts | 24 ++ .../lists/common/schemas/response/index.ts | 4 + .../exceptions_list_so_schema.ts | 50 ++++ .../common/schemas/saved_objects/index.ts | 6 + .../schemas/types/default_entries_array.ts | 28 ++ .../schemas/types/default_string_array.ts | 22 ++ .../common/schemas/types/default_uuid.ts | 26 ++ .../lists/common/schemas/types/entries.ts | 21 ++ .../lists/common/schemas/types/index.ts | 10 + .../plugins/lists/common/siem_common_deps.ts | 6 +- x-pack/plugins/lists/server/plugin.ts | 26 +- .../create_exception_list_item_route.ts | 109 ++++++++ .../routes/create_exception_list_route.ts | 82 ++++++ .../delete_exception_list_item_route.ts | 70 +++++ .../routes/delete_exception_list_route.ts | 71 +++++ .../server/routes/delete_list_item_route.ts | 2 +- .../routes/find_exception_list_item_route.ts | 73 +++++ .../routes/find_exception_list_route.ts | 65 +++++ x-pack/plugins/lists/server/routes/index.ts | 12 + .../lists/server/routes/init_routes.ts | 31 ++- .../routes/read_exception_list_item_route.ts | 68 +++++ .../routes/read_exception_list_route.ts | 68 +++++ .../server/routes/read_list_item_route.ts | 2 +- .../update_exception_list_item_route.ts | 89 +++++++ .../routes/update_exception_list_route.ts | 83 ++++++ .../utils/get_error_message_exception_list.ts | 21 ++ .../get_error_message_exception_list_item.ts | 21 ++ .../routes/utils/get_exception_list_client.ts | 19 ++ .../server/routes/utils/get_list_client.ts | 2 +- .../lists/server/routes/utils/index.ts | 3 + .../server/saved_objects/exception_list.ts | 115 ++++++++ .../lists/server/saved_objects/index.ts | 7 + .../saved_objects/init_saved_objects.ts | 14 + .../scripts/delete_all_exception_lists.sh | 33 +++ .../server/scripts/delete_exception_list.sh | 16 ++ .../scripts/delete_exception_list_by_id.sh | 16 ++ .../scripts/delete_exception_list_item.sh | 16 ++ .../delete_exception_list_item_by_id.sh | 16 ++ .../lists/server/scripts/delete_list.sh | 2 +- .../exception_lists/new/exception_list.json | 8 + .../new/exception_list_auto_id.json | 5 + .../new/exception_list_item.json | 21 ++ .../new/exception_list_item_auto_id.json | 20 ++ .../updates/simple_update.json | 8 + .../updates/simple_update_item.json | 15 ++ .../scripts/find_exception_list_items.sh | 16 ++ .../server/scripts/find_exception_lists.sh | 15 ++ .../server/scripts/get_exception_list.sh | 15 ++ .../scripts/get_exception_list_by_id.sh | 15 ++ .../server/scripts/get_exception_list_item.sh | 15 ++ .../scripts/get_exception_list_item_by_id.sh | 15 ++ .../lists/server/scripts/hard_reset.sh | 3 + .../server/scripts/post_exception_list.sh | 30 +++ .../scripts/post_exception_list_item.sh | 30 +++ .../server/scripts/update_exception_list.sh | 30 +++ .../scripts/update_exception_list_item.sh | 30 +++ .../exception_lists/create_exception_list.ts | 72 +++++ .../create_exception_list_item.ts | 81 ++++++ .../exception_lists/delete_exception_list.ts | 42 +++ .../delete_exception_list_item.ts | 41 +++ .../delete_exception_list_items_by_list.ts | 89 +++++++ .../exception_lists/exception_list_client.ts | 250 ++++++++++++++++++ .../exception_list_client_types.ts | 130 +++++++++ .../exception_lists/find_exception_list.ts | 58 ++++ .../find_exception_list_item.ts | 77 ++++++ .../exception_lists/get_exception_list.ts | 64 +++++ .../get_exception_list_item.ts | 66 +++++ .../server/services/exception_lists/index.ts | 16 ++ .../server/services/exception_lists/types.ts | 6 + .../exception_lists/update_exception_list.ts | 74 ++++++ .../update_exception_list_item.ts | 87 ++++++ .../server/services/exception_lists/utils.ts | 235 ++++++++++++++++ .../lists/{client.ts => list_client.ts} | 2 +- .../{client_types.ts => list_client_types.ts} | 0 .../lists/server/services/utils/index.ts | 2 +- .../plugins/lists/server/siem_server_deps.ts | 35 +-- x-pack/plugins/lists/server/types.ts | 23 +- .../exact_check.test.ts | 2 +- .../exact_check.ts | 0 .../format_errors.test.ts | 0 .../format_errors.ts | 0 .../utils.ts => common/test_utils.ts} | 2 +- x-pack/plugins/siem/server/index.ts | 14 + .../detection_engine/routes/rules/validate.ts | 4 +- .../response/check_type_dependents.test.ts | 4 +- .../schemas/response/error_schema.test.ts | 4 +- .../response/find_rules_schema.test.ts | 4 +- .../response/import_rules_schema.test.ts | 4 +- .../response/prepackaged_rules_schema.test.ts | 4 +- .../prepackaged_rules_status_schema.test.ts | 4 +- .../response/rules_bulk_schema.test.ts | 4 +- .../schemas/response/rules_schema.test.ts | 4 +- .../type_timeline_only_schema.test.ts | 4 +- .../schemas/types/iso_date_string.test.ts | 2 +- .../schemas/types/lists_default_array.test.ts | 2 +- ...positive_integer_greater_than_zero.test.ts | 2 +- .../schemas/types/postive_integer.test.ts | 2 +- .../types/references_default_array.test.ts | 2 +- .../routes/schemas/types/risk_score.test.ts | 2 +- .../routes/schemas/types/uuid.test.ts | 2 +- .../build_validation/route_validation.ts | 4 +- 130 files changed, 3800 insertions(+), 106 deletions(-) create mode 100644 x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/saved_objects/exceptions_list_so_schema.ts create mode 100644 x-pack/plugins/lists/common/schemas/saved_objects/index.ts create mode 100644 x-pack/plugins/lists/common/schemas/types/default_entries_array.ts create mode 100644 x-pack/plugins/lists/common/schemas/types/default_string_array.ts create mode 100644 x-pack/plugins/lists/common/schemas/types/default_uuid.ts create mode 100644 x-pack/plugins/lists/common/schemas/types/entries.ts create mode 100644 x-pack/plugins/lists/common/schemas/types/index.ts create mode 100644 x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts create mode 100644 x-pack/plugins/lists/server/routes/create_exception_list_route.ts create mode 100644 x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts create mode 100644 x-pack/plugins/lists/server/routes/delete_exception_list_route.ts create mode 100644 x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts create mode 100644 x-pack/plugins/lists/server/routes/find_exception_list_route.ts create mode 100644 x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts create mode 100644 x-pack/plugins/lists/server/routes/read_exception_list_route.ts create mode 100644 x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts create mode 100644 x-pack/plugins/lists/server/routes/update_exception_list_route.ts create mode 100644 x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list.ts create mode 100644 x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list_item.ts create mode 100644 x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts create mode 100644 x-pack/plugins/lists/server/saved_objects/exception_list.ts create mode 100644 x-pack/plugins/lists/server/saved_objects/index.ts create mode 100644 x-pack/plugins/lists/server/saved_objects/init_saved_objects.ts create mode 100755 x-pack/plugins/lists/server/scripts/delete_all_exception_lists.sh create mode 100755 x-pack/plugins/lists/server/scripts/delete_exception_list.sh create mode 100755 x-pack/plugins/lists/server/scripts/delete_exception_list_by_id.sh create mode 100755 x-pack/plugins/lists/server/scripts/delete_exception_list_item.sh create mode 100755 x-pack/plugins/lists/server/scripts/delete_exception_list_item_by_id.sh create mode 100644 x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json create mode 100644 x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_auto_id.json create mode 100644 x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item.json create mode 100644 x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item_auto_id.json create mode 100644 x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json create mode 100644 x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json create mode 100755 x-pack/plugins/lists/server/scripts/find_exception_list_items.sh create mode 100755 x-pack/plugins/lists/server/scripts/find_exception_lists.sh create mode 100755 x-pack/plugins/lists/server/scripts/get_exception_list.sh create mode 100755 x-pack/plugins/lists/server/scripts/get_exception_list_by_id.sh create mode 100755 x-pack/plugins/lists/server/scripts/get_exception_list_item.sh create mode 100755 x-pack/plugins/lists/server/scripts/get_exception_list_item_by_id.sh create mode 100755 x-pack/plugins/lists/server/scripts/post_exception_list.sh create mode 100755 x-pack/plugins/lists/server/scripts/post_exception_list_item.sh create mode 100755 x-pack/plugins/lists/server/scripts/update_exception_list.sh create mode 100755 x-pack/plugins/lists/server/scripts/update_exception_list_item.sh create mode 100644 x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/index.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/types.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts create mode 100644 x-pack/plugins/lists/server/services/exception_lists/utils.ts rename x-pack/plugins/lists/server/services/lists/{client.ts => list_client.ts} (99%) rename x-pack/plugins/lists/server/services/lists/{client_types.ts => list_client_types.ts} (100%) rename x-pack/plugins/siem/{server/utils/build_validation => common}/exact_check.test.ts (98%) rename x-pack/plugins/siem/{server/utils/build_validation => common}/exact_check.ts (100%) rename x-pack/plugins/siem/{server/utils/build_validation => common}/format_errors.test.ts (100%) rename x-pack/plugins/siem/{server/utils/build_validation => common}/format_errors.ts (100%) rename x-pack/plugins/siem/{server/utils/build_validation/__mocks__/utils.ts => common/test_utils.ts} (95%) diff --git a/x-pack/plugins/lists/common/constants.ts b/x-pack/plugins/lists/common/constants.ts index dbe31fed66413..96d28bf618ce4 100644 --- a/x-pack/plugins/lists/common/constants.ts +++ b/x-pack/plugins/lists/common/constants.ts @@ -5,8 +5,14 @@ */ /** - * Lists routes + * Value list routes */ -export const LIST_URL = `/api/lists`; +export const LIST_URL = '/api/lists'; export const LIST_INDEX = `${LIST_URL}/index`; export const LIST_ITEM_URL = `${LIST_URL}/items`; + +/** + * Exception list routes + */ +export const EXCEPTION_LIST_URL = '/api/exception_lists'; +export const EXCEPTION_LIST_ITEM_URL = '/api/exception_lists/items'; diff --git a/x-pack/plugins/lists/common/schemas/common/schemas.ts b/x-pack/plugins/lists/common/schemas/common/schemas.ts index edc037ed7a0b1..cd69685ffcb1b 100644 --- a/x-pack/plugins/lists/common/schemas/common/schemas.ts +++ b/x-pack/plugins/lists/common/schemas/common/schemas.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { NonEmptyString } from '../types/non_empty_string'; +import { DefaultStringArray, NonEmptyString } from '../types'; export const name = t.string; export type Name = t.TypeOf; @@ -21,8 +21,9 @@ export const descriptionOrUndefined = t.union([description, t.undefined]); export type DescriptionOrUndefined = t.TypeOf; export const list_id = NonEmptyString; +export type ListId = t.TypeOf; export const list_idOrUndefined = t.union([list_id, t.undefined]); -export type List_idOrUndefined = t.TypeOf; +export type ListIdOrUndefined = t.TypeOf; export const item = t.string; export const created_at = t.string; // TODO: Make this into an ISO Date string check @@ -60,3 +61,44 @@ export type MetaOrUndefined = t.TypeOf; export const esDataTypeUnion = t.union([t.type({ ip }), t.type({ keyword })]); export type EsDataTypeUnion = t.TypeOf; + +export const tags = DefaultStringArray; +export type Tags = t.TypeOf; +export const tagsOrUndefined = t.union([tags, t.undefined]); +export type TagsOrUndefined = t.TypeOf; + +export const _tags = DefaultStringArray; +export type _Tags = t.TypeOf; +export const _tagsOrUndefined = t.union([_tags, t.undefined]); +export type _TagsOrUndefined = t.TypeOf; + +// TODO: Change this into a t.keyof enumeration when we know what types of lists we going to have. +export const exceptionListType = t.string; +export const exceptionListTypeOrUndefined = t.union([exceptionListType, t.undefined]); +export type ExceptionListType = t.TypeOf; +export type ExceptionListTypeOrUndefined = t.TypeOf; + +// TODO: Change this into a t.keyof enumeration when we know what types of lists we going to have. +export const exceptionListItemType = t.string; +export type ExceptionListItemType = t.TypeOf; + +export const list_type = t.keyof({ item: null, list: null }); +export type ListType = t.TypeOf; + +// TODO: Investigate what the deep structure of a comment is really going to be and then change this to use that deep structure with a default array +export const comment = DefaultStringArray; +export type Comment = t.TypeOf; +export const commentOrUndefined = t.union([comment, t.undefined]); +export type CommentOrUndefined = t.TypeOf; + +export const item_id = NonEmptyString; +export type ItemId = t.TypeOf; +export const itemIdOrUndefined = t.union([item_id, t.undefined]); +export type ItemIdOrUndefined = t.TypeOf; + +export const per_page = t.number; // TODO: Change this out for PositiveNumber from siem +export const total = t.number; // TODO: Change this out for PositiveNumber from siem +export const page = t.number; // TODO: Change this out for PositiveNumber from siem +export const sort_field = t.string; +export const sort_order = t.keyof({ asc: null, desc: null }); +export const filter = t.string; diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts b/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts index 8f23f3744e563..d008a82308153 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts @@ -16,6 +16,7 @@ import { updated_by, } from '../common/schemas'; +// TODO: Should we use partial here and everywhere these are instead of this OrUndefined? export const updateEsListSchema = t.exact( t.type({ description: descriptionOrUndefined, diff --git a/x-pack/plugins/lists/common/schemas/index.ts b/x-pack/plugins/lists/common/schemas/index.ts index 6a60a6df55691..774378c6318f5 100644 --- a/x-pack/plugins/lists/common/schemas/index.ts +++ b/x-pack/plugins/lists/common/schemas/index.ts @@ -5,7 +5,9 @@ */ export * from './common'; -export * from './request'; -export * from './response'; export * from './elastic_query'; export * from './elastic_response'; +export * from './request'; +export * from './response'; +export * from './saved_objects'; +export * from './types'; diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts new file mode 100644 index 0000000000000..f899fd69110fa --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + ItemId, + Tags, + _Tags, + _tags, + comment, + description, + exceptionListItemType, + list_id, + meta, + name, + tags, +} from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; +import { DefaultEntryArray, DefaultUuid } from '../types'; +import { EntriesArray } from '../types/entries'; + +export const createExceptionListItemSchema = t.intersection([ + t.exact( + t.type({ + description, + list_id, + name, + type: exceptionListItemType, + }) + ), + t.exact( + t.partial({ + _tags, // defaults to empty array if not set during decode + comment, // defaults to empty array if not set during decode + entries: DefaultEntryArray, // defaults to empty array if not set during decode + item_id: DefaultUuid, // defaults to GUID (uuid v4) if not set during decode + meta, // defaults to undefined if not set during decode + tags, // defaults to empty array if not set during decode + }) + ), +]); + +export type CreateExceptionListItemSchemaPartial = Identity< + t.TypeOf +>; +export type CreateExceptionListItemSchema = RequiredKeepUndefined< + t.TypeOf +>; + +// This type is used after a decode since the arrays turn into defaults of empty arrays +// and if a item_id is not specified it turns into a default GUID +export type CreateExceptionListItemSchemaDecoded = Identity< + Omit & { + _tags: _Tags; + tags: Tags; + item_id: ItemId; + entries: EntriesArray; + } +>; diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts new file mode 100644 index 0000000000000..d38d3cc038525 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DESCRIPTION, LIST_ID, META, NAME, TYPE } from '../../constants.mock'; + +import { CreateExceptionListSchema } from './create_exception_list_schema'; + +export const getCreateExceptionListSchemaMock = (): CreateExceptionListSchema => ({ + _tags: [], + description: DESCRIPTION, + list_id: LIST_ID, + meta: META, + name: NAME, + tags: [], + type: TYPE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts new file mode 100644 index 0000000000000..f19c50c6b42ae --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { + CreateExceptionListSchema, + createExceptionListSchema, +} from './create_exception_list_schema'; +import { getCreateExceptionListSchemaMock } from './create_exception_list_schema.mock'; + +describe('create_exception_list_schema', () => { + test('it should validate a typical exception lists request', () => { + const payload = getCreateExceptionListSchemaMock(); + const decoded = createExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for meta', () => { + const payload = getCreateExceptionListSchemaMock(); + delete payload.meta; + const decoded = createExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for tags but return an array', () => { + const inputPayload = getCreateExceptionListSchemaMock(); + const outputPayload = getCreateExceptionListSchemaMock(); + delete inputPayload.tags; + outputPayload.tags = []; + const decoded = createExceptionListSchema.decode(inputPayload); + const checked = exactCheck(inputPayload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(outputPayload); + }); + + test('it should accept an undefined for _tags but return an array', () => { + const inputPayload = getCreateExceptionListSchemaMock(); + const outputPayload = getCreateExceptionListSchemaMock(); + delete inputPayload._tags; + outputPayload._tags = []; + const decoded = createExceptionListSchema.decode(inputPayload); + const checked = exactCheck(inputPayload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(outputPayload); + }); + + test('it should accept an undefined for list_id and auto generate a uuid', () => { + const inputPayload = getCreateExceptionListSchemaMock(); + delete inputPayload.list_id; + const decoded = createExceptionListSchema.decode(inputPayload); + const checked = exactCheck(inputPayload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect((message.schema as CreateExceptionListSchema).list_id).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i + ); + }); + + test('it should accept an undefined for list_id and generate a correct body not counting the uuid', () => { + const inputPayload = getCreateExceptionListSchemaMock(); + delete inputPayload.list_id; + const decoded = createExceptionListSchema.decode(inputPayload); + const checked = exactCheck(inputPayload, decoded); + const message = pipe(checked, foldLeftRight); + delete (message.schema as CreateExceptionListSchema).list_id; + expect(message.schema).toEqual(inputPayload); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: CreateExceptionListSchema & { + extraKey?: string; + } = getCreateExceptionListSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = createExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts new file mode 100644 index 0000000000000..5ba3bf4e8f43b --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + ListId, + Tags, + _Tags, + _tags, + description, + exceptionListType, + meta, + name, + tags, +} from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; +import { DefaultUuid } from '../types/default_uuid'; + +export const createExceptionListSchema = t.intersection([ + t.exact( + t.type({ + description, + name, + type: exceptionListType, + }) + ), + t.exact( + t.partial({ + _tags, // defaults to empty array if not set during decode + list_id: DefaultUuid, // defaults to a GUID (UUID v4) string if not set during decode + meta, // defaults to undefined if not set during decode + tags, // defaults to empty array if not set during decode + }) + ), +]); + +export type CreateExceptionListSchemaPartial = Identity>; +export type CreateExceptionListSchema = RequiredKeepUndefined< + t.TypeOf +>; + +// This type is used after a decode since the arrays turn into defaults of empty arrays. +export type CreateExceptionListSchemaDecoded = Identity< + CreateExceptionListSchema & { + _tags: _Tags; + tags: Tags; + list_id: ListId; + } +>; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts index 6cba81e47fbcc..6d16f2074864c 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { idOrUndefined, list_id, metaOrUndefined, value } from '../common/schemas'; +import { id, list_id, meta, value } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const createListItemSchema = t.intersection([ @@ -18,7 +18,7 @@ export const createListItemSchema = t.intersection([ value, }) ), - t.exact(t.partial({ id: idOrUndefined, meta: metaOrUndefined })), + t.exact(t.partial({ id, meta })), ]); export type CreateListItemSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts index 7a6e2a707873c..68df80b2a42dd 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts @@ -6,7 +6,7 @@ import * as t from 'io-ts'; -import { description, idOrUndefined, metaOrUndefined, name, type } from '../common/schemas'; +import { description, id, meta, name, type } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const createListSchema = t.intersection([ @@ -17,7 +17,7 @@ export const createListSchema = t.intersection([ type, }) ), - t.exact(t.partial({ id: idOrUndefined, meta: metaOrUndefined })), + t.exact(t.partial({ id, meta })), ]); export type CreateListSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts new file mode 100644 index 0000000000000..607e05ef8286f --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { id, item_id } from '../common/schemas'; + +export const deleteExceptionListItemSchema = t.exact( + t.partial({ + id, + item_id, + }) +); + +export type DeleteExceptionListItemSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts new file mode 100644 index 0000000000000..7a6086514f943 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { id, list_id } from '../common/schemas'; + +export const deleteExceptionListSchema = t.exact( + t.partial({ + id, + list_id, + }) +); + +export type DeleteExceptionListSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts index 96f054b304962..91887395e747d 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { idOrUndefined, list_idOrUndefined, valueOrUndefined } from '../common/schemas'; +import { id, list_id, valueOrUndefined } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const deleteListItemSchema = t.intersection([ @@ -17,7 +17,7 @@ export const deleteListItemSchema = t.intersection([ value: valueOrUndefined, }) ), - t.exact(t.partial({ id: idOrUndefined, list_id: list_idOrUndefined })), + t.exact(t.partial({ id, list_id })), ]); export type DeleteListItemSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts new file mode 100644 index 0000000000000..3fc51dd20b0b3 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { filter, list_id, page, per_page, sort_field, sort_order } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; + +export const findExceptionListItemSchema = t.intersection([ + t.exact( + t.type({ + list_id, + }) + ), + t.exact( + t.partial({ + filter, // defaults to undefined if not set during decode + page, // defaults to undefined if not set during decode + per_page, // defaults to undefined if not set during decode + sort_field, // defaults to undefined if not set during decode + sort_order, // defaults to undefined if not set during decode + }) + ), +]); + +export type FindExceptionListItemSchemaPartial = t.TypeOf; + +export type FindExceptionListItemSchema = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts new file mode 100644 index 0000000000000..f795be9493fbf --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { filter, page, per_page, sort_field, sort_order } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; + +export const findExceptionListSchema = t.exact( + t.partial({ + filter, // defaults to undefined if not set during decode + page, // defaults to undefined if not set during decode + per_page, // defaults to undefined if not set during decode + sort_field, // defaults to undefined if not set during decode + sort_order, // defaults to undefined if not set during decode + }) +); + +export type FindExceptionListSchemaPartial = t.TypeOf; + +export type FindExceptionListSchema = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts index c1745dda7afab..73d9a53a41e4f 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts @@ -8,12 +8,10 @@ import * as t from 'io-ts'; -import { list_idOrUndefined, typeOrUndefined } from '../common/schemas'; +import { list_id, type } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; -export const importListItemQuerySchema = t.exact( - t.partial({ list_id: list_idOrUndefined, type: typeOrUndefined }) -); +export const importListItemQuerySchema = t.exact(t.partial({ list_id, type })); export type ImportListItemQuerySchemaPartial = Identity>; export type ImportListItemQuerySchema = RequiredKeepUndefined< diff --git a/x-pack/plugins/lists/common/schemas/request/index.ts b/x-pack/plugins/lists/common/schemas/request/index.ts index d332ab1eb1bab..0dbd9297b773e 100644 --- a/x-pack/plugins/lists/common/schemas/request/index.ts +++ b/x-pack/plugins/lists/common/schemas/request/index.ts @@ -4,16 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './create_exception_list_item_schema'; +export * from './create_exception_list_schema'; export * from './create_list_item_schema'; export * from './create_list_schema'; +export * from './delete_exception_list_item_schema'; +export * from './delete_exception_list_schema'; export * from './delete_list_item_schema'; export * from './delete_list_schema'; export * from './export_list_item_query_schema'; +export * from './find_exception_list_item_schema'; +export * from './find_exception_list_schema'; export * from './import_list_item_schema'; export * from './patch_list_item_schema'; export * from './patch_list_schema'; +export * from './read_exception_list_item_schema'; +export * from './read_exception_list_schema'; export * from './read_list_item_schema'; export * from './read_list_schema'; +export * from './update_exception_list_item_schema'; +export * from './update_exception_list_schema'; export * from './import_list_item_query_schema'; export * from './update_list_schema'; +export * from './update_exception_list_schema'; export * from './update_list_item_schema'; diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts index 536931f715f3f..2016069f32fb3 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { id, metaOrUndefined, valueOrUndefined } from '../common/schemas'; +import { id, meta, value } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const patchListItemSchema = t.intersection([ @@ -17,7 +17,7 @@ export const patchListItemSchema = t.intersection([ id, }) ), - t.exact(t.partial({ meta: metaOrUndefined, value: valueOrUndefined })), + t.exact(t.partial({ meta, value })), ]); export type PatchListItemSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts index 59d1a66a581a0..653a42dc91653 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { descriptionOrUndefined, id, metaOrUndefined, nameOrUndefined } from '../common/schemas'; +import { description, id, meta, name } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const patchListSchema = t.intersection([ @@ -17,9 +17,7 @@ export const patchListSchema = t.intersection([ id, }) ), - t.exact( - t.partial({ description: descriptionOrUndefined, meta: metaOrUndefined, name: nameOrUndefined }) - ), + t.exact(t.partial({ description, meta, name })), ]); export type PatchListSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts new file mode 100644 index 0000000000000..095fcd2f63b48 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { id, item_id } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; + +export const readExceptionListItemSchema = t.partial({ + id, + item_id, +}); + +export type ReadExceptionListItemSchemaPartial = t.TypeOf; +export type ReadExceptionListItemSchema = RequiredKeepUndefined; diff --git a/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts new file mode 100644 index 0000000000000..5593e640f71ac --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { id, list_id } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; + +export const readExceptionListSchema = t.partial({ + id, + list_id, +}); + +export type ReadExceptionListSchemaPartial = t.TypeOf; +export type ReadExceptionListSchema = RequiredKeepUndefined; diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts index b69523b664fd7..394c1f1e2289a 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts @@ -8,12 +8,10 @@ import * as t from 'io-ts'; -import { idOrUndefined, list_idOrUndefined, valueOrUndefined } from '../common/schemas'; +import { id, list_id, value } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; -export const readListItemSchema = t.exact( - t.partial({ id: idOrUndefined, list_id: list_idOrUndefined, value: valueOrUndefined }) -); +export const readListItemSchema = t.exact(t.partial({ id, list_id, value })); export type ReadListItemSchemaPartial = Identity>; export type ReadListItemSchema = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts new file mode 100644 index 0000000000000..162406a6d6589 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + Tags, + _Tags, + _tags, + comment, + description, + exceptionListItemType, + id, + meta, + name, + tags, +} from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; +import { DefaultEntryArray } from '../types'; +import { EntriesArray } from '../types/entries'; + +export const updateExceptionListItemSchema = t.intersection([ + t.exact( + t.type({ + description, + name, + type: exceptionListItemType, + }) + ), + t.exact( + t.partial({ + _tags, // defaults to empty array if not set during decode + comment, // defaults to empty array if not set during decode + entries: DefaultEntryArray, // defaults to empty array if not set during decode + id, // defaults to undefined if not set during decode + item_id: t.union([t.string, t.undefined]), + meta, // defaults to undefined if not set during decode + tags, // defaults to empty array if not set during decode + }) + ), +]); + +export type UpdateExceptionListItemSchemaPartial = Identity< + t.TypeOf +>; +export type UpdateExceptionListItemSchema = RequiredKeepUndefined< + t.TypeOf +>; + +// This type is used after a decode since the arrays turn into defaults of empty arrays +// and if a item_id is not specified it turns into a default GUID +export type UpdateExceptionListItemSchemaDecoded = Identity< + Omit & { + _tags: _Tags; + tags: Tags; + entries: EntriesArray; + } +>; diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts new file mode 100644 index 0000000000000..e8a0dcd4994a2 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + Tags, + _Tags, + _tags, + description, + exceptionListType, + meta, + name, + tags, +} from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; + +export const updateExceptionListSchema = t.intersection([ + t.exact( + t.type({ + description, + name, + type: exceptionListType, + }) + ), + t.exact( + t.partial({ + _tags, // defaults to empty array if not set during decode + id: t.union([t.string, t.undefined]), // defaults to undefined if not set during decode + list_id: t.union([t.string, t.undefined]), // defaults to undefined if not set during decode + meta, // defaults to undefined if not set during decode + tags, // defaults to empty array if not set during decode + }) + ), +]); + +export type UpdateExceptionListSchemaPartial = Identity>; +export type UpdateExceptionListSchema = RequiredKeepUndefined< + t.TypeOf +>; + +// This type is used after a decode since the arrays turn into defaults of empty arrays. +export type UpdateExceptionListSchemaDecoded = Identity< + Omit & { + _tags: _Tags; + tags: Tags; + } +>; diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts index 23701ff753bc0..3a42cf28665f5 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { id, metaOrUndefined, value } from '../common/schemas'; +import { id, meta, value } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const updateListItemSchema = t.intersection([ @@ -18,7 +18,11 @@ export const updateListItemSchema = t.intersection([ value, }) ), - t.exact(t.partial({ meta: metaOrUndefined })), + t.exact( + t.partial({ + meta, // defaults to undefined if not set during decode + }) + ), ]); export type UpdateListItemSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts index 8223a6a34b771..4c5c8c429a14c 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { description, id, metaOrUndefined, name } from '../common/schemas'; +import { description, id, meta, name } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const updateListSchema = t.intersection([ @@ -19,7 +19,11 @@ export const updateListSchema = t.intersection([ name, }) ), - t.exact(t.partial({ meta: metaOrUndefined })), + t.exact( + t.partial({ + meta, // defaults to undefined if not set during decode + }) + ), ]); export type UpdateListSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts new file mode 100644 index 0000000000000..15e1c92c06d13 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + _tags, + commentOrUndefined, + created_at, + created_by, + description, + exceptionListItemType, + id, + item_id, + list_id, + metaOrUndefined, + name, + tags, + tie_breaker_id, + updated_at, + updated_by, +} from '../common/schemas'; +import { entriesArray } from '../types'; + +// TODO: Should we use a partial here to reflect that this can JSON serialize meta, comment as non existent? +export const exceptionListItemSchema = t.exact( + t.type({ + _tags, + comment: commentOrUndefined, + created_at, + created_by, + description, + entries: entriesArray, + id, + item_id, + list_id, + meta: metaOrUndefined, + name, + tags, + tie_breaker_id, + type: exceptionListItemType, + updated_at, + updated_by, + }) +); + +export type ExceptionListItemSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts new file mode 100644 index 0000000000000..1940d94597dec --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + _tags, + created_at, + created_by, + description, + exceptionListType, + id, + list_id, + metaOrUndefined, + name, + tags, + tie_breaker_id, + updated_at, + updated_by, +} from '../common/schemas'; + +// TODO: Should we use a partial here to reflect that this can JSON serialize meta as non existent? +export const exceptionListSchema = t.exact( + t.type({ + _tags, + created_at, + created_by, + description, + id, + list_id, + meta: metaOrUndefined, + name, + tags, + tie_breaker_id, + type: exceptionListType, + updated_at, + updated_by, + }) +); + +export type ExceptionListSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.ts new file mode 100644 index 0000000000000..a58bf433017e6 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { page, per_page, total } from '../common/schemas'; + +import { exceptionListItemSchema } from './exception_list_item_schema'; + +export const foundExceptionListItemSchema = t.exact( + t.type({ + data: t.array(exceptionListItemSchema), + page, + per_page, + total, + }) +); + +export type FoundExceptionListItemSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.ts new file mode 100644 index 0000000000000..a2ea09a3263ae --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { page, per_page, total } from '../common/schemas'; + +import { exceptionListSchema } from './exception_list_schema'; + +export const foundExceptionListSchema = t.exact( + t.type({ + data: t.array(exceptionListSchema), + page, + per_page, + total, + }) +); + +export type FoundExceptionListSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/response/index.ts b/x-pack/plugins/lists/common/schemas/response/index.ts index 3f11adf58d8d4..213685d1183bd 100644 --- a/x-pack/plugins/lists/common/schemas/response/index.ts +++ b/x-pack/plugins/lists/common/schemas/response/index.ts @@ -8,3 +8,7 @@ export * from './list_item_schema'; export * from './list_schema'; export * from './acknowledge_schema'; export * from './list_item_index_exist_schema'; +export * from './exception_list_schema'; +export * from './found_exception_list_item_schema'; +export * from './found_exception_list_schema'; +export * from './exception_list_item_schema'; diff --git a/x-pack/plugins/lists/common/schemas/saved_objects/exceptions_list_so_schema.ts b/x-pack/plugins/lists/common/schemas/saved_objects/exceptions_list_so_schema.ts new file mode 100644 index 0000000000000..a08bab65c881c --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/saved_objects/exceptions_list_so_schema.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { entriesArrayOrUndefined } from '../types'; +import { + _tags, + commentOrUndefined, + created_at, + created_by, + description, + exceptionListItemType, + exceptionListType, + itemIdOrUndefined, + list_id, + list_type, + metaOrUndefined, + name, + tags, + tie_breaker_id, + updated_by, +} from '../common/schemas'; + +export const exceptionListSoSchema = t.exact( + t.type({ + _tags, + comment: commentOrUndefined, + created_at, + created_by, + description, + entries: entriesArrayOrUndefined, + item_id: itemIdOrUndefined, + list_id, + list_type, + meta: metaOrUndefined, + name, + tags, + tie_breaker_id, + type: t.union([exceptionListType, exceptionListItemType]), + updated_by, + }) +); + +export type ExceptionListSoSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/saved_objects/index.ts b/x-pack/plugins/lists/common/schemas/saved_objects/index.ts new file mode 100644 index 0000000000000..8d42be2d145ca --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/saved_objects/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './exceptions_list_so_schema'; diff --git a/x-pack/plugins/lists/common/schemas/types/default_entries_array.ts b/x-pack/plugins/lists/common/schemas/types/default_entries_array.ts new file mode 100644 index 0000000000000..43698665bb371 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/default_entries_array.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { Either } from 'fp-ts/lib/Either'; + +import { EntriesArray, entries } from './entries'; + +export type DefaultEntriesArrayC = t.Type; + +/** + * Types the DefaultEntriesArray as: + * - If null or undefined, then a default array of type entry will be set + */ +export const DefaultEntryArray: DefaultEntriesArrayC = new t.Type< + EntriesArray, + EntriesArray, + unknown +>( + 'DefaultEntryArray', + t.array(entries).is, + (input): Either => + input == null ? t.success([]) : t.array(entries).decode(input), + t.identity +); diff --git a/x-pack/plugins/lists/common/schemas/types/default_string_array.ts b/x-pack/plugins/lists/common/schemas/types/default_string_array.ts new file mode 100644 index 0000000000000..c6ebffa379903 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/default_string_array.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { Either } from 'fp-ts/lib/Either'; + +export type DefaultStringArrayC = t.Type; + +/** + * Types the DefaultStringArray as: + * - If null or undefined, then a default array will be set + */ +export const DefaultStringArray: DefaultStringArrayC = new t.Type( + 'DefaultArray', + t.array(t.string).is, + (input): Either => + input == null ? t.success([]) : t.array(t.string).decode(input), + t.identity +); diff --git a/x-pack/plugins/lists/common/schemas/types/default_uuid.ts b/x-pack/plugins/lists/common/schemas/types/default_uuid.ts new file mode 100644 index 0000000000000..4fb4133d7353f --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/default_uuid.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { Either } from 'fp-ts/lib/Either'; +import uuid from 'uuid'; + +import { NonEmptyString } from './non_empty_string'; + +export type DefaultUuidC = t.Type; + +/** + * Types the DefaultUuid as: + * - If null or undefined, then a default string uuid.v4() will be + * created otherwise it will be checked just against an empty string + */ +export const DefaultUuid: DefaultUuidC = new t.Type( + 'DefaultUuid', + t.string.is, + (input): Either => + input == null ? t.success(uuid.v4()) : NonEmptyString.decode(input), + t.identity +); diff --git a/x-pack/plugins/lists/common/schemas/types/entries.ts b/x-pack/plugins/lists/common/schemas/types/entries.ts new file mode 100644 index 0000000000000..750e04e2f39e5 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/entries.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; + +export const entries = t.exact( + t.type({ + field: t.string, + match: t.union([t.string, t.undefined]), + match_any: t.union([t.array(t.string), t.undefined]), + operator: t.string, // TODO: Use a key of with all possible values + }) +); + +export const entriesArray = t.array(entries); +export type EntriesArray = t.TypeOf; +export type Entries = t.TypeOf; +export const entriesArrayOrUndefined = t.union([entriesArray, t.undefined]); +export type EntriesArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/index.ts b/x-pack/plugins/lists/common/schemas/types/index.ts new file mode 100644 index 0000000000000..674d7c40c2970 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './default_entries_array'; +export * from './default_string_array'; +export * from './default_uuid'; +export * from './entries'; +export * from './non_empty_string'; diff --git a/x-pack/plugins/lists/common/siem_common_deps.ts b/x-pack/plugins/lists/common/siem_common_deps.ts index c5242843dfbdd..eb4b393a36da3 100644 --- a/x-pack/plugins/lists/common/siem_common_deps.ts +++ b/x-pack/plugins/lists/common/siem_common_deps.ts @@ -4,7 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { getPaths, foldLeftRight } from '../../siem/server/utils/build_validation/__mocks__/utils'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { exactCheck } from '../../siem/server/utils/build_validation/exact_check'; +export { exactCheck } from '../../siem/common/exact_check'; +export { getPaths, foldLeftRight } from '../../siem/common/test_utils'; diff --git a/x-pack/plugins/lists/server/plugin.ts b/x-pack/plugins/lists/server/plugin.ts index 5facf981c098e..ed515757875be 100644 --- a/x-pack/plugins/lists/server/plugin.ts +++ b/x-pack/plugins/lists/server/plugin.ts @@ -13,7 +13,7 @@ import { SpacesServiceSetup } from '../../spaces/server'; import { ConfigType } from './config'; import { initRoutes } from './routes/init_routes'; -import { ListClient } from './services/lists/client'; +import { ListClient } from './services/lists/list_client'; import { ContextProvider, ContextProviderReturn, @@ -24,6 +24,8 @@ import { import { createConfig$ } from './create_config'; import { getSpaceId } from './get_space_id'; import { getUser } from './get_user'; +import { initSavedObjects } from './saved_objects'; +import { ExceptionListClient } from './services/exception_lists/exception_list_client'; export class ListPlugin implements Plugin, ListsPluginStart, PluginsSetup> { @@ -48,14 +50,22 @@ export class ListPlugin this.config = config; this.security = plugins.security; + initSavedObjects(core.savedObjects); + core.http.registerRouteHandlerContext('lists', this.createRouteHandlerContext()); const router = core.http.createRouter(); initRoutes(router); return { - getListClient: (apiCaller, spaceId, user): ListClient => { + getExceptionListClient: (savedObjectsClient, user): ExceptionListClient => { + return new ExceptionListClient({ + savedObjectsClient, + user, + }); + }, + getListClient: (callCluster, spaceId, user): ListClient => { return new ListClient({ - callCluster: apiCaller, + callCluster, config, spaceId, user, @@ -77,8 +87,9 @@ export class ListPlugin const { spaces, config, security } = this; const { core: { + savedObjects: { client: savedObjectsClient }, elasticsearch: { - dataClient: { callAsCurrentUser }, + dataClient: { callAsCurrentUser: callCluster }, }, }, } = context; @@ -88,9 +99,14 @@ export class ListPlugin const spaceId = getSpaceId({ request, spaces }); const user = getUser({ request, security }); return { + getExceptionListClient: (): ExceptionListClient => + new ExceptionListClient({ + savedObjectsClient, + user, + }), getListClient: (): ListClient => new ListClient({ - callCluster: callAsCurrentUser, + callCluster, config, spaceId, user, diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts new file mode 100644 index 0000000000000..ddcae137a961a --- /dev/null +++ b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { + CreateExceptionListItemSchemaDecoded, + createExceptionListItemSchema, + exceptionListItemSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from './utils/get_exception_list_client'; + +export const createExceptionListItemRoute = (router: IRouter): void => { + router.post( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_ITEM_URL, + validate: { + body: buildRouteValidation< + typeof createExceptionListItemSchema, + CreateExceptionListItemSchemaDecoded + >(createExceptionListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { + name, + _tags, + tags, + meta, + comment, + description, + entries, + item_id: itemId, + list_id: listId, + type, + } = request.body; + const exceptionLists = getExceptionListClient(context); + const exceptionList = await exceptionLists.getExceptionList({ + id: undefined, + listId, + // TODO: Expose the name space type + namespaceType: 'single', + }); + if (exceptionList == null) { + return siemResponse.error({ + body: `list id: "${listId}" does not exist`, + statusCode: 404, + }); + } else { + const exceptionListItem = await exceptionLists.getExceptionListItem({ + id: undefined, + itemId, + // TODO: Expose the name space type + namespaceType: 'single', + }); + if (exceptionListItem != null) { + return siemResponse.error({ + body: `exception list item id: "${itemId}" already exists`, + statusCode: 409, + }); + } else { + const createdList = await exceptionLists.createExceptionListItem({ + _tags, + comment, + description, + entries, + itemId, + listId, + meta, + name, + // TODO: Expose the name space type + namespaceType: 'single', + tags, + type, + }); + const [validated, errors] = validate(createdList, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts new file mode 100644 index 0000000000000..c8a1b080c16f6 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { + CreateExceptionListSchemaDecoded, + createExceptionListSchema, + exceptionListSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from './utils/get_exception_list_client'; + +export const createExceptionListRoute = (router: IRouter): void => { + router.post( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_URL, + validate: { + body: buildRouteValidation< + typeof createExceptionListSchema, + CreateExceptionListSchemaDecoded + >(createExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { name, _tags, tags, meta, description, list_id: listId, type } = request.body; + const exceptionLists = getExceptionListClient(context); + const exceptionList = await exceptionLists.getExceptionList({ + id: undefined, + listId, + // TODO: Expose the name space type + namespaceType: 'single', + }); + if (exceptionList != null) { + return siemResponse.error({ + body: `exception list id: "${listId}" already exists`, + statusCode: 409, + }); + } else { + const createdList = await exceptionLists.createExceptionList({ + _tags, + description, + listId, + meta, + name, + // TODO: Expose the name space type + namespaceType: 'single', + tags, + type, + }); + const [validated, errors] = validate(createdList, exceptionListSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts new file mode 100644 index 0000000000000..e10ffab5359b0 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { deleteExceptionListItemSchema, exceptionListItemSchema } from '../../common/schemas'; + +import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils'; + +export const deleteExceptionListItemRoute = (router: IRouter): void => { + router.delete( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_ITEM_URL, + validate: { + query: buildRouteValidation(deleteExceptionListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const exceptionLists = getExceptionListClient(context); + const { item_id: itemId, id } = request.query; + if (itemId == null && id == null) { + return siemResponse.error({ + body: 'Either "item_id" or "id" needs to be defined in the request', + statusCode: 400, + }); + } else { + const deleted = await exceptionLists.deleteExceptionListItem({ + id, + itemId, + namespaceType: 'single', // TODO: Bubble this up + }); + if (deleted == null) { + return siemResponse.error({ + body: getErrorMessageExceptionListItem({ id, itemId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(deleted, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts new file mode 100644 index 0000000000000..ef30ab6ab64c5 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { deleteExceptionListSchema, exceptionListSchema } from '../../common/schemas'; + +import { getErrorMessageExceptionList, getExceptionListClient } from './utils'; + +export const deleteExceptionListRoute = (router: IRouter): void => { + router.delete( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_URL, + validate: { + query: buildRouteValidation(deleteExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const exceptionLists = getExceptionListClient(context); + const { list_id: listId, id } = request.query; + if (listId == null && id == null) { + return siemResponse.error({ + body: 'Either "list_id" or "id" needs to be defined in the request', + statusCode: 400, + }); + } else { + // TODO: At the moment this will delete the list but we need to delete all the list items before deleting the list + const deleted = await exceptionLists.deleteExceptionList({ + id, + listId, + namespaceType: 'single', + }); + if (deleted == null) { + return siemResponse.error({ + body: getErrorMessageExceptionList({ id, listId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(deleted, exceptionListSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts index 51b4eb9f02cc2..82dfe8a4f29d0 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts @@ -73,7 +73,7 @@ export const deleteListItemRoute = (router: IRouter): void => { } } else { return siemResponse.error({ - body: `Either "list_id" or "id" needs to be defined in the request`, + body: 'Either "list_id" or "id" needs to be defined in the request', statusCode: 400, }); } diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts new file mode 100644 index 0000000000000..3b5503ffb9833 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { findExceptionListItemSchema, foundExceptionListItemSchema } from '../../common/schemas'; + +import { getExceptionListClient } from './utils'; + +export const findExceptionListItemRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists'], + }, + path: `${EXCEPTION_LIST_ITEM_URL}/_find`, + validate: { + query: buildRouteValidation(findExceptionListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const exceptionLists = getExceptionListClient(context); + const { + filter, + list_id: listId, + page, + per_page: perPage, + sort_field: sortField, + sort_order: sortOrder, + } = request.query; + const exceptionListItems = await exceptionLists.findExceptionListItem({ + filter, + listId, + namespaceType: 'single', // TODO: Bubble this up + page, + perPage, + sortField, + sortOrder, + }); + if (exceptionListItems == null) { + return siemResponse.error({ + body: `list id: "${listId}" does not exist`, + statusCode: 404, + }); + } + const [validated, errors] = validate(exceptionListItems, foundExceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts new file mode 100644 index 0000000000000..41c0c0760e03b --- /dev/null +++ b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { findExceptionListSchema, foundExceptionListSchema } from '../../common/schemas'; + +import { getExceptionListClient } from './utils'; + +export const findExceptionListRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists'], + }, + path: `${EXCEPTION_LIST_URL}/_find`, + validate: { + query: buildRouteValidation(findExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const exceptionLists = getExceptionListClient(context); + const { + filter, + page, + per_page: perPage, + sort_field: sortField, + sort_order: sortOrder, + } = request.query; + const exceptionListItems = await exceptionLists.findExceptionList({ + filter, + namespaceType: 'single', // TODO: Bubble this up + page, + perPage, + sortField, + sortOrder, + }); + const [validated, errors] = validate(exceptionListItems, foundExceptionListSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/index.ts b/x-pack/plugins/lists/server/routes/index.ts index 4951cddc56939..97f497bca7183 100644 --- a/x-pack/plugins/lists/server/routes/index.ts +++ b/x-pack/plugins/lists/server/routes/index.ts @@ -4,18 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './create_exception_list_item_route'; +export * from './create_exception_list_route'; export * from './create_list_index_route'; export * from './create_list_item_route'; export * from './create_list_route'; +export * from './delete_exception_list_route'; +export * from './delete_exception_list_item_route'; export * from './delete_list_index_route'; export * from './delete_list_item_route'; export * from './delete_list_route'; export * from './export_list_item_route'; +export * from './find_exception_list_item_route'; +export * from './find_exception_list_route'; export * from './import_list_item_route'; export * from './init_routes'; export * from './patch_list_item_route'; export * from './patch_list_route'; +export * from './read_exception_list_item_route'; +export * from './read_exception_list_route'; export * from './read_list_index_route'; export * from './read_list_item_route'; export * from './read_list_route'; +export * from './update_exception_list_item_route'; +export * from './update_exception_list_route'; +export * from './update_list_item_route'; +export * from './update_list_route'; export * from './utils'; diff --git a/x-pack/plugins/lists/server/routes/init_routes.ts b/x-pack/plugins/lists/server/routes/init_routes.ts index 924dd086ee708..16f96d99505d8 100644 --- a/x-pack/plugins/lists/server/routes/init_routes.ts +++ b/x-pack/plugins/lists/server/routes/init_routes.ts @@ -6,23 +6,32 @@ import { IRouter } from 'kibana/server'; -import { updateListRoute } from './update_list_route'; -import { updateListItemRoute } from './update_list_item_route'; - import { + createExceptionListItemRoute, + createExceptionListRoute, createListIndexRoute, createListItemRoute, createListRoute, + deleteExceptionListItemRoute, + deleteExceptionListRoute, deleteListIndexRoute, deleteListItemRoute, deleteListRoute, exportListItemRoute, + findExceptionListItemRoute, + findExceptionListRoute, importListItemRoute, patchListItemRoute, patchListRoute, + readExceptionListItemRoute, + readExceptionListRoute, readListIndexRoute, readListItemRoute, readListRoute, + updateExceptionListItemRoute, + updateExceptionListRoute, + updateListItemRoute, + updateListRoute, } from '.'; export const initRoutes = (router: IRouter): void => { @@ -33,7 +42,7 @@ export const initRoutes = (router: IRouter): void => { deleteListRoute(router); patchListRoute(router); - // lists items + // list items createListItemRoute(router); readListItemRoute(router); updateListItemRoute(router); @@ -46,4 +55,18 @@ export const initRoutes = (router: IRouter): void => { createListIndexRoute(router); readListIndexRoute(router); deleteListIndexRoute(router); + + // exception lists + createExceptionListRoute(router); + readExceptionListRoute(router); + updateExceptionListRoute(router); + deleteExceptionListRoute(router); + findExceptionListRoute(router); + + // exception list items + createExceptionListItemRoute(router); + readExceptionListItemRoute(router); + updateExceptionListItemRoute(router); + deleteExceptionListItemRoute(router); + findExceptionListItemRoute(router); }; diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts new file mode 100644 index 0000000000000..77d37373549c7 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { exceptionListItemSchema, readExceptionListItemSchema } from '../../common/schemas'; + +import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils'; + +export const readExceptionListItemRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_ITEM_URL, + validate: { + query: buildRouteValidation(readExceptionListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { id, item_id: itemId } = request.query; + const exceptionLists = getExceptionListClient(context); + if (id != null || itemId != null) { + const exceptionListItem = await exceptionLists.getExceptionListItem({ + id, + itemId, + // TODO: Bubble this up + namespaceType: 'single', + }); + if (exceptionListItem == null) { + return siemResponse.error({ + body: getErrorMessageExceptionListItem({ id, itemId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(exceptionListItem, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } else { + return siemResponse.error({ body: 'id or item_id required', statusCode: 400 }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts new file mode 100644 index 0000000000000..1668124acdfce --- /dev/null +++ b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { exceptionListSchema, readExceptionListSchema } from '../../common/schemas'; + +import { getErrorMessageExceptionList, getExceptionListClient } from './utils'; + +export const readExceptionListRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_URL, + validate: { + query: buildRouteValidation(readExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { id, list_id: listId } = request.query; + const exceptionLists = getExceptionListClient(context); + if (id != null || listId != null) { + const exceptionList = await exceptionLists.getExceptionList({ + id, + listId, + // TODO: Bubble this up + namespaceType: 'single', + }); + if (exceptionList == null) { + return siemResponse.error({ + body: getErrorMessageExceptionList({ id, listId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(exceptionList, exceptionListSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } else { + return siemResponse.error({ body: 'id or list_id required', statusCode: 400 }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/read_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_list_item_route.ts index 0a60cba786f04..10c7f781f554c 100644 --- a/x-pack/plugins/lists/server/routes/read_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_item_route.ts @@ -77,7 +77,7 @@ export const readListItemRoute = (router: IRouter): void => { } } else { return siemResponse.error({ - body: `Either "list_id" or "id" needs to be defined in the request`, + body: 'Either "list_id" or "id" needs to be defined in the request', statusCode: 400, }); } diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts new file mode 100644 index 0000000000000..478225ee35eb8 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { + UpdateExceptionListItemSchemaDecoded, + exceptionListItemSchema, + updateExceptionListItemSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from '.'; + +export const updateExceptionListItemRoute = (router: IRouter): void => { + router.put( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_ITEM_URL, + validate: { + body: buildRouteValidation< + typeof updateExceptionListItemSchema, + UpdateExceptionListItemSchemaDecoded + >(updateExceptionListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { + description, + id, + name, + meta, + type, + _tags, + comment, + entries, + item_id: itemId, + tags, + } = request.body; + const exceptionLists = getExceptionListClient(context); + const exceptionListItem = await exceptionLists.updateExceptionListItem({ + _tags, + comment, + description, + entries, + id, + itemId, + meta, + name, + namespaceType: 'single', // TODO: Bubble this up + tags, + type, + }); + if (exceptionListItem == null) { + return siemResponse.error({ + body: `list item id: "${id}" not found`, + statusCode: 404, + }); + } else { + const [validated, errors] = validate(exceptionListItem, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts new file mode 100644 index 0000000000000..a112c7422b952 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { + UpdateExceptionListSchemaDecoded, + exceptionListSchema, + updateExceptionListSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from './utils'; + +export const updateExceptionListRoute = (router: IRouter): void => { + router.put( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_URL, + validate: { + body: buildRouteValidation< + typeof updateExceptionListSchema, + UpdateExceptionListSchemaDecoded + >(updateExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { _tags, tags, name, description, id, list_id: listId, meta, type } = request.body; + const exceptionLists = getExceptionListClient(context); + if (id == null && listId == null) { + return siemResponse.error({ + body: `either id or list_id need to be defined`, + statusCode: 404, + }); + } else { + const list = await exceptionLists.updateExceptionList({ + _tags, + description, + id, + listId, + meta, + name, + namespaceType: 'single', // TODO: Bubble this up + tags, + type, + }); + if (list == null) { + return siemResponse.error({ + body: `exception list id: "${id}" found found`, + statusCode: 404, + }); + } else { + const [validated, errors] = validate(list, exceptionListSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list.ts b/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list.ts new file mode 100644 index 0000000000000..665a7540184a0 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const getErrorMessageExceptionList = ({ + id, + listId, +}: { + id: string | undefined; + listId: string | undefined; +}): string => { + if (id != null) { + return `Exception list id: "${id}" does not exist`; + } else if (listId != null) { + return `Exception list list_id: "${listId}" does not exist`; + } else { + return 'Exception list does not exist'; + } +}; diff --git a/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list_item.ts b/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list_item.ts new file mode 100644 index 0000000000000..8e6730ef3db5c --- /dev/null +++ b/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list_item.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const getErrorMessageExceptionListItem = ({ + id, + itemId, +}: { + id: string | undefined; + itemId: string | undefined; +}): string => { + if (id != null) { + return `Exception list item id: "${id}" does not exist`; + } else if (itemId != null) { + return `Exception list item list_id: "${itemId}" does not exist`; + } else { + return 'Exception list item does not exist'; + } +}; diff --git a/x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts b/x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts new file mode 100644 index 0000000000000..ba01ca617fb8b --- /dev/null +++ b/x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext } from 'kibana/server'; + +import { ErrorWithStatusCode } from '../../error_with_status_code'; +import { ExceptionListClient } from '../../services/exception_lists/exception_list_client'; + +export const getExceptionListClient = (context: RequestHandlerContext): ExceptionListClient => { + const exceptionLists = context.lists?.getExceptionListClient(); + if (exceptionLists == null) { + throw new ErrorWithStatusCode('Exception lists is not found as a plugin', 404); + } else { + return exceptionLists; + } +}; diff --git a/x-pack/plugins/lists/server/routes/utils/get_list_client.ts b/x-pack/plugins/lists/server/routes/utils/get_list_client.ts index a16163ec0fa3a..6ad69fd994bfd 100644 --- a/x-pack/plugins/lists/server/routes/utils/get_list_client.ts +++ b/x-pack/plugins/lists/server/routes/utils/get_list_client.ts @@ -6,7 +6,7 @@ import { RequestHandlerContext } from 'kibana/server'; -import { ListClient } from '../../services/lists/client'; +import { ListClient } from '../../services/lists/list_client'; import { ErrorWithStatusCode } from '../../error_with_status_code'; export const getListClient = (context: RequestHandlerContext): ListClient => { diff --git a/x-pack/plugins/lists/server/routes/utils/index.ts b/x-pack/plugins/lists/server/routes/utils/index.ts index a601bdfc003c5..90214872b68e3 100644 --- a/x-pack/plugins/lists/server/routes/utils/index.ts +++ b/x-pack/plugins/lists/server/routes/utils/index.ts @@ -3,4 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +export * from './get_error_message_exception_list_item'; +export * from './get_error_message_exception_list'; export * from './get_list_client'; +export * from './get_exception_list_client'; diff --git a/x-pack/plugins/lists/server/saved_objects/exception_list.ts b/x-pack/plugins/lists/server/saved_objects/exception_list.ts new file mode 100644 index 0000000000000..9e1a708e0c83b --- /dev/null +++ b/x-pack/plugins/lists/server/saved_objects/exception_list.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsType } from 'kibana/server'; + +export const exceptionListSavedObjectType = 'exception-list'; +export const exceptionListAgnosticSavedObjectType = 'exception-list-agnostic'; +export type SavedObjectType = 'exception-list' | 'exception-list-agnostic'; + +/** + * This is a super set of exception list and exception list items. The switch + * to determine if you are using an exception list vs. an exception list item + * is "list_type". If "list_type" is "list" then it is an exception list. If + * "list_type" is "item" then the type is an item. + */ +export const commonMapping: SavedObjectsType['mappings'] = { + properties: { + _tags: { + type: 'keyword', + }, + created_at: { + type: 'keyword', + }, + created_by: { + type: 'keyword', + }, + description: { + type: 'keyword', + }, + list_id: { + type: 'keyword', + }, + list_type: { + type: 'keyword', + }, + meta: { + type: 'keyword', + }, + name: { + type: 'keyword', + }, + tags: { + type: 'keyword', + }, + tie_breaker_id: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + updated_by: { + type: 'keyword', + }, + }, +}; + +export const exceptionListMapping: SavedObjectsType['mappings'] = { + properties: { + // There is nothing that is not also used within exceptionListItemMapping + // at this time but if there is it should go here + }, +}; + +export const exceptionListItemMapping: SavedObjectsType['mappings'] = { + properties: { + comment: { + // TODO: investigate what the deep mapping structure of this really is + type: 'keyword', + }, + entries: { + properties: { + field: { + type: 'keyword', + }, + match: { + type: 'keyword', + }, + match_any: { + type: 'keyword', + }, + operator: { + type: 'keyword', + }, + }, + }, + item_id: { + type: 'keyword', + }, + }, +}; + +const combinedMappings: SavedObjectsType['mappings'] = { + properties: { + ...commonMapping.properties, + ...exceptionListMapping.properties, + ...exceptionListItemMapping.properties, + }, +}; + +export const exceptionListType: SavedObjectsType = { + hidden: false, + mappings: combinedMappings, + name: exceptionListSavedObjectType, + namespaceType: 'single', +}; + +export const exceptionListAgnosticType: SavedObjectsType = { + hidden: false, + mappings: combinedMappings, + name: exceptionListAgnosticSavedObjectType, + namespaceType: 'agnostic', +}; diff --git a/x-pack/plugins/lists/server/saved_objects/index.ts b/x-pack/plugins/lists/server/saved_objects/index.ts new file mode 100644 index 0000000000000..b5077ae100cde --- /dev/null +++ b/x-pack/plugins/lists/server/saved_objects/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './exception_list'; +export * from './init_saved_objects'; diff --git a/x-pack/plugins/lists/server/saved_objects/init_saved_objects.ts b/x-pack/plugins/lists/server/saved_objects/init_saved_objects.ts new file mode 100644 index 0000000000000..aab5ffd0e57fe --- /dev/null +++ b/x-pack/plugins/lists/server/saved_objects/init_saved_objects.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup } from 'kibana/server'; + +import { exceptionListAgnosticType, exceptionListType } from './exception_list'; + +export const initSavedObjects = (savedObjects: CoreSetup['savedObjects']): void => { + savedObjects.registerType(exceptionListAgnosticType); + savedObjects.registerType(exceptionListType); +}; diff --git a/x-pack/plugins/lists/server/scripts/delete_all_exception_lists.sh b/x-pack/plugins/lists/server/scripts/delete_all_exception_lists.sh new file mode 100755 index 0000000000000..bb431800c56c3 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_all_exception_lists.sh @@ -0,0 +1,33 @@ +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_all_alerts.sh +# https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html +curl -s -k \ + -H "Content-Type: application/json" \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${ELASTICSEARCH_URL}/${KIBANA_INDEX}*/_delete_by_query \ + --data '{ + "query": { + "exists": { "field": "exception-list" } + } + }' \ + | jq . + +# Deletes all the agnostic namespace version +curl -s -k \ + -H "Content-Type: application/json" \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${ELASTICSEARCH_URL}/${KIBANA_INDEX}*/_delete_by_query \ + --data '{ + "query": { + "exists": { "field": "exception-list-agnostic" } + } + }' \ + | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_exception_list.sh b/x-pack/plugins/lists/server/scripts/delete_exception_list.sh new file mode 100755 index 0000000000000..fe2ca501b4416 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_exception_list.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_exception_list.sh ${list_id} +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists?list_id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_exception_list_by_id.sh b/x-pack/plugins/lists/server/scripts/delete_exception_list_by_id.sh new file mode 100755 index 0000000000000..a87881b385328 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_exception_list_by_id.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_exception_list_by_id.sh ${list_id} +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists?id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_exception_list_item.sh b/x-pack/plugins/lists/server/scripts/delete_exception_list_item.sh new file mode 100755 index 0000000000000..7e09452a23e11 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_exception_list_item.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_exception_list_item.sh ${item_id} +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?item_id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_exception_list_item_by_id.sh b/x-pack/plugins/lists/server/scripts/delete_exception_list_item_by_id.sh new file mode 100755 index 0000000000000..bbfbc3135ddb8 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_exception_list_item_by_id.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_exception_list_item_by_id.sh ${list_id} +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_list.sh b/x-pack/plugins/lists/server/scripts/delete_list.sh index 9934ce61c7107..ce9fdd6aa21d4 100755 --- a/x-pack/plugins/lists/server/scripts/delete_list.sh +++ b/x-pack/plugins/lists/server/scripts/delete_list.sh @@ -9,7 +9,7 @@ set -e ./check_env_variables.sh -# Example: ./delete_list_by_list_id.sh ${list_id} +# Example: ./delete_list.sh ${list_id} curl -s -k \ -H 'kbn-xsrf: 123' \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json new file mode 100644 index 0000000000000..520bc4ddf1e09 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json @@ -0,0 +1,8 @@ +{ + "list_id": "endpoint_list", + "_tags": ["endpoint", "process", "malware", "os:linux"], + "tags": ["user added string for a tag", "malware"], + "type": "endpoint", + "description": "This is a sample endpoint type exception", + "name": "Sample Endpoint Exception List" +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_auto_id.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_auto_id.json new file mode 100644 index 0000000000000..8c039bf6788ae --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_auto_id.json @@ -0,0 +1,5 @@ +{ + "description": "This is a sample exception list with auto created list_id since none was provided", + "name": "Sample Exception List without a whole lot going on", + "type": "endpoint" +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item.json new file mode 100644 index 0000000000000..2b97f37a7fa6b --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item.json @@ -0,0 +1,21 @@ +{ + "list_id": "endpoint_list", + "item_id": "endpoint_list_item", + "_tags": ["endpoint", "process", "malware", "os:linux"], + "tags": ["user added string for a tag", "malware"], + "type": "simple", + "description": "This is a sample endpoint type exception", + "name": "Sample Endpoint Exception List", + "entries": [ + { + "field": "actingProcess.file.signer", + "operator": "included", + "match": "Elastic, N.V." + }, + { + "field": "event.category", + "operator": "included", + "match_any": ["process", "malware"] + } + ] +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item_auto_id.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item_auto_id.json new file mode 100644 index 0000000000000..d68a26eb8ffe2 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item_auto_id.json @@ -0,0 +1,20 @@ +{ + "list_id": "endpoint_list", + "_tags": ["endpoint", "process", "malware", "os:linux"], + "tags": ["user added string for a tag", "malware"], + "type": "simple", + "description": "This is a sample endpoint type exception that has no item_id so it creates a new id each time", + "name": "Sample Endpoint Exception List", + "entries": [ + { + "field": "actingProcess.file.signer", + "operator": "included", + "match": "Elastic, N.V." + }, + { + "field": "event.category", + "operator": "included", + "match_any": ["process", "malware"] + } + ] +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json new file mode 100644 index 0000000000000..a7fbe1ea48c02 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json @@ -0,0 +1,8 @@ +{ + "list_id": "endpoint_list", + "_tags": ["endpoint", "process", "malware", "os:linux"], + "tags": ["user added string for a tag", "malware"], + "type": "endpoint", + "description": "Different description", + "name": "Sample Endpoint Exception List" +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json new file mode 100644 index 0000000000000..a53079318edfa --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json @@ -0,0 +1,15 @@ +{ + "item_id": "endpoint_list_item", + "_tags": ["endpoint", "process", "malware", "os:windows"], + "tags": ["user added string for a tag", "malware"], + "type": "simple", + "description": "This is a sample change here this list", + "name": "Sample Endpoint Exception List update change", + "entries": [ + { + "field": "event.category", + "operator": "included", + "match_any": ["process", "malware"] + } + ] +} diff --git a/x-pack/plugins/lists/server/scripts/find_exception_list_items.sh b/x-pack/plugins/lists/server/scripts/find_exception_list_items.sh new file mode 100755 index 0000000000000..85c5b0e518fab --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/find_exception_list_items.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +LIST_ID=${1:-endpoint_list} +# Example: ./find_exception_list_items.sh {list-id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items/_find?list_id=${LIST_ID} | jq . diff --git a/x-pack/plugins/lists/server/scripts/find_exception_lists.sh b/x-pack/plugins/lists/server/scripts/find_exception_lists.sh new file mode 100755 index 0000000000000..a1ee184b3e5bb --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/find_exception_lists.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./find_exception_lists.sh {list-id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/_find | jq . diff --git a/x-pack/plugins/lists/server/scripts/get_exception_list.sh b/x-pack/plugins/lists/server/scripts/get_exception_list.sh new file mode 100755 index 0000000000000..34e6de2576879 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/get_exception_list.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./get_exception_list.sh {id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists?list_id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/get_exception_list_by_id.sh b/x-pack/plugins/lists/server/scripts/get_exception_list_by_id.sh new file mode 100755 index 0000000000000..0420a1f702328 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/get_exception_list_by_id.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./get_exception_list_by_id.sh {id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists?id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/get_exception_list_item.sh b/x-pack/plugins/lists/server/scripts/get_exception_list_item.sh new file mode 100755 index 0000000000000..ac8337aab8368 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/get_exception_list_item.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./get_exception_list_item.sh {id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?item_id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/get_exception_list_item_by_id.sh b/x-pack/plugins/lists/server/scripts/get_exception_list_item_by_id.sh new file mode 100755 index 0000000000000..575a529c69906 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/get_exception_list_item_by_id.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./get_exception_list_item_by_id.sh {id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/hard_reset.sh b/x-pack/plugins/lists/server/scripts/hard_reset.sh index 861928866369b..e00c3a9ace34f 100755 --- a/x-pack/plugins/lists/server/scripts/hard_reset.sh +++ b/x-pack/plugins/lists/server/scripts/hard_reset.sh @@ -12,3 +12,6 @@ set -e # re-create the list and list item indexes ./delete_list_index.sh ./post_list_index.sh + +# re-create the exception list and exception list items +./delete_all_exception_lists.sh diff --git a/x-pack/plugins/lists/server/scripts/post_exception_list.sh b/x-pack/plugins/lists/server/scripts/post_exception_list.sh new file mode 100755 index 0000000000000..84a775ffcf7f1 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/post_exception_list.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/new/exception_list.json}) + +# Example: ./post_exception_list.sh +# Example: ./post_exception_list.sh ./exception_lists/new/exception_list.json +for LIST in "${LISTS[@]}" +do { + [ -e "$LIST" ] || continue + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/exception_lists \ + -d @${LIST} \ + | jq .; +} & +done + +wait diff --git a/x-pack/plugins/lists/server/scripts/post_exception_list_item.sh b/x-pack/plugins/lists/server/scripts/post_exception_list_item.sh new file mode 100755 index 0000000000000..6cee54b1a6148 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/post_exception_list_item.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/new/exception_list_item.json}) + +# Example: ./post_exception_list_item.sh +# Example: ./post_exception_list_item.sh ./exception_lists/new/exception_list_item.json +for LIST in "${LISTS[@]}" +do { + [ -e "$LIST" ] || continue + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items \ + -d @${LIST} \ + | jq .; +} & +done + +wait diff --git a/x-pack/plugins/lists/server/scripts/update_exception_list.sh b/x-pack/plugins/lists/server/scripts/update_exception_list.sh new file mode 100755 index 0000000000000..d7523a0804a89 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/update_exception_list.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/updates/simple_update.json}) + +# Example: ./update_exception_list.sh +# Example: ./update_exception_list.sh ./exception_lists/updates/simple_update.json +for LIST in "${LISTS[@]}" +do { + [ -e "$LIST" ] || continue + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X PUT ${KIBANA_URL}${SPACE_URL}/api/exception_lists \ + -d @${LIST} \ + | jq .; +} & +done + +wait diff --git a/x-pack/plugins/lists/server/scripts/update_exception_list_item.sh b/x-pack/plugins/lists/server/scripts/update_exception_list_item.sh new file mode 100755 index 0000000000000..029bfcdabee3e --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/update_exception_list_item.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/updates/simple_update_item.json}) + +# Example: ./update_exception_list_item.sh +# Example: ./update_exception_list_item.sh ./exception_lists/updates/simple_update_item.json +for LIST in "${LISTS[@]}" +do { + [ -e "$LIST" ] || continue + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X PUT ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items \ + -d @${LIST} \ + | jq .; +} & +done + +wait diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts new file mode 100644 index 0000000000000..7ba832e72bb8e --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; +import uuid from 'uuid'; + +import { + Description, + ExceptionListSchema, + ExceptionListSoSchema, + ExceptionListType, + ListId, + MetaOrUndefined, + Name, + Tags, + _Tags, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils'; +import { NamespaceType } from './types'; + +interface CreateExceptionListOptions { + _tags: _Tags; + listId: ListId; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; + name: Name; + description: Description; + meta: MetaOrUndefined; + user: string; + tags: Tags; + tieBreaker?: string; + type: ExceptionListType; +} + +export const createExceptionList = async ({ + _tags, + listId, + savedObjectsClient, + namespaceType, + name, + description, + meta, + user, + tags, + tieBreaker, + type, +}: CreateExceptionListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const dateNow = new Date().toISOString(); + const savedObject = await savedObjectsClient.create(savedObjectType, { + _tags, + comment: undefined, + created_at: dateNow, + created_by: user, + description, + entries: undefined, + item_id: undefined, + list_id: listId, + list_type: 'list', + meta, + name, + tags, + tie_breaker_id: tieBreaker ?? uuid.v4(), + type, + updated_by: user, + }); + return transformSavedObjectToExceptionList({ savedObject }); +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts new file mode 100644 index 0000000000000..4a6dc1da97854 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; +import uuid from 'uuid'; + +import { + CommentOrUndefined, + Description, + EntriesArray, + ExceptionListItemSchema, + ExceptionListSoSchema, + ExceptionListType, + ItemId, + ListId, + MetaOrUndefined, + Name, + Tags, + _Tags, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectToExceptionListItem } from './utils'; +import { NamespaceType } from './types'; + +interface CreateExceptionListItemOptions { + _tags: _Tags; + comment: CommentOrUndefined; + listId: ListId; + itemId: ItemId; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; + name: Name; + description: Description; + entries: EntriesArray; + meta: MetaOrUndefined; + user: string; + tags: Tags; + tieBreaker?: string; + type: ExceptionListType; +} + +export const createExceptionListItem = async ({ + _tags, + comment, + entries, + itemId, + listId, + savedObjectsClient, + namespaceType, + name, + description, + meta, + user, + tags, + tieBreaker, + type, +}: CreateExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const dateNow = new Date().toISOString(); + const savedObject = await savedObjectsClient.create(savedObjectType, { + _tags, + comment, + created_at: dateNow, + created_by: user, + description, + entries, + item_id: itemId, + list_id: listId, + list_type: 'item', + meta, + name, + tags, + tie_breaker_id: tieBreaker ?? uuid.v4(), + type, + updated_by: user, + }); + return transformSavedObjectToExceptionListItem({ savedObject }); +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts new file mode 100644 index 0000000000000..6904438c8d275 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; + +import { ExceptionListSchema, IdOrUndefined, ListIdOrUndefined } from '../../../common/schemas'; + +import { getSavedObjectType } from './utils'; +import { NamespaceType } from './types'; +import { getExceptionList } from './get_exception_list'; +import { deleteExceptionListItemByList } from './delete_exception_list_items_by_list'; + +interface DeleteExceptionListOptions { + listId: ListIdOrUndefined; + id: IdOrUndefined; + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; +} + +export const deleteExceptionList = async ({ + listId, + id, + namespaceType, + savedObjectsClient, +}: DeleteExceptionListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionList = await getExceptionList({ id, listId, namespaceType, savedObjectsClient }); + if (exceptionList == null) { + return null; + } else { + await deleteExceptionListItemByList({ + listId: exceptionList.list_id, + namespaceType, + savedObjectsClient, + }); + await savedObjectsClient.delete(savedObjectType, exceptionList.id); + return exceptionList; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts new file mode 100644 index 0000000000000..3b2d991281cd6 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; + +import { ExceptionListItemSchema, IdOrUndefined, ItemIdOrUndefined } from '../../../common/schemas'; + +import { getSavedObjectType } from './utils'; +import { NamespaceType } from './types'; +import { getExceptionListItem } from './get_exception_list_item'; + +interface DeleteExceptionListItemOptions { + itemId: ItemIdOrUndefined; + id: IdOrUndefined; + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; +} + +export const deleteExceptionListItem = async ({ + itemId, + id, + namespaceType, + savedObjectsClient, +}: DeleteExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionListItem = await getExceptionListItem({ + id, + itemId, + namespaceType, + savedObjectsClient, + }); + if (exceptionListItem == null) { + return null; + } else { + await savedObjectsClient.delete(savedObjectType, exceptionListItem.id); + return exceptionListItem; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts new file mode 100644 index 0000000000000..8031f085c1635 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from '../../../../../../src/core/server/'; +import { ListId } from '../../../common/schemas'; + +import { findExceptionListItem } from './find_exception_list_item'; +import { NamespaceType } from './types'; +import { getSavedObjectType } from './utils'; + +const PER_PAGE = 100; + +interface DeleteExceptionListItemByListOptions { + listId: ListId; + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; +} + +export const deleteExceptionListItemByList = async ({ + listId, + savedObjectsClient, + namespaceType, +}: DeleteExceptionListItemByListOptions): Promise => { + const ids = await getExceptionListItemIds({ listId, namespaceType, savedObjectsClient }); + await deleteFoundExceptionListItems({ ids, namespaceType, savedObjectsClient }); +}; + +export const getExceptionListItemIds = async ({ + listId, + savedObjectsClient, + namespaceType, +}: DeleteExceptionListItemByListOptions): Promise => { + let page = 1; + let ids: string[] = []; + let foundExceptionListItems = await findExceptionListItem({ + filter: undefined, + listId, + namespaceType, + page, + perPage: PER_PAGE, + savedObjectsClient, + sortField: 'tie_breaker_id', + sortOrder: 'desc', + }); + while (foundExceptionListItems != null && foundExceptionListItems.data.length > 0) { + ids = [...ids, ...foundExceptionListItems.data.map(exceptionListItem => exceptionListItem.id)]; + page += 1; + foundExceptionListItems = await findExceptionListItem({ + filter: undefined, + listId, + namespaceType, + page, + perPage: PER_PAGE, + savedObjectsClient, + sortField: 'tie_breaker_id', + sortOrder: 'desc', + }); + } + return ids; +}; + +/** + * NOTE: This is slow and terrible as we are deleting everything one at a time. + * TODO: Replace this with a bulk call or a delete by query would be more useful + */ +export const deleteFoundExceptionListItems = async ({ + ids, + savedObjectsClient, + namespaceType, +}: { + ids: string[]; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; +}): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + ids.forEach(async id => { + try { + await savedObjectsClient.delete(savedObjectType, id); + } catch (err) { + // This can happen from race conditions or networking issues so deleting the id's + // like this is considered "best effort" and it is possible to get dangling pieces + // of data sitting around in which case the user has to manually clean up the data + // I am very hopeful this does not happen often or at all. + } + }); +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts new file mode 100644 index 0000000000000..6e71ed1b3e59d --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts @@ -0,0 +1,250 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; + +import { + ExceptionListSchema, + FoundExceptionListItemSchema, + FoundExceptionListSchema, +} from '../../../common/schemas'; + +import { + ConstructorOptions, + CreateExceptionListItemOptions, + CreateExceptionListOptions, + DeleteExceptionListItemOptions, + DeleteExceptionListOptions, + FindExceptionListItemOptions, + FindExceptionListOptions, + GetExceptionListItemOptions, + GetExceptionListOptions, + UpdateExceptionListItemOptions, + UpdateExceptionListOptions, +} from './exception_list_client_types'; +import { getExceptionList } from './get_exception_list'; +import { createExceptionList } from './create_exception_list'; +import { getExceptionListItem } from './get_exception_list_item'; +import { createExceptionListItem } from './create_exception_list_item'; +import { updateExceptionList } from './update_exception_list'; +import { updateExceptionListItem } from './update_exception_list_item'; +import { deleteExceptionList } from './delete_exception_list'; +import { deleteExceptionListItem } from './delete_exception_list_item'; +import { findExceptionListItem } from './find_exception_list_item'; +import { findExceptionList } from './find_exception_list'; + +export class ExceptionListClient { + private readonly user: string; + + private readonly savedObjectsClient: SavedObjectsClientContract; + + constructor({ user, savedObjectsClient }: ConstructorOptions) { + this.user = user; + this.savedObjectsClient = savedObjectsClient; + } + + public getExceptionList = async ({ + listId, + id, + namespaceType, + }: GetExceptionListOptions): Promise => { + const { savedObjectsClient } = this; + return getExceptionList({ id, listId, namespaceType, savedObjectsClient }); + }; + + public getExceptionListItem = async ({ + itemId, + id, + namespaceType, + }: GetExceptionListItemOptions): Promise => { + const { savedObjectsClient } = this; + return getExceptionListItem({ id, itemId, namespaceType, savedObjectsClient }); + }; + + public createExceptionList = async ({ + _tags, + description, + listId, + meta, + name, + namespaceType, + tags, + type, + }: CreateExceptionListOptions): Promise => { + const { savedObjectsClient, user } = this; + return createExceptionList({ + _tags, + description, + listId, + meta, + name, + namespaceType, + savedObjectsClient, + tags, + type, + user, + }); + }; + + public updateExceptionList = async ({ + _tags, + id, + description, + listId, + meta, + name, + namespaceType, + tags, + type, + }: UpdateExceptionListOptions): Promise => { + const { savedObjectsClient, user } = this; + return updateExceptionList({ + _tags, + description, + id, + listId, + meta, + name, + namespaceType, + savedObjectsClient, + tags, + type, + user, + }); + }; + + public deleteExceptionList = async ({ + id, + listId, + namespaceType, + }: DeleteExceptionListOptions): Promise => { + const { savedObjectsClient } = this; + return deleteExceptionList({ + id, + listId, + namespaceType, + savedObjectsClient, + }); + }; + + public createExceptionListItem = async ({ + _tags, + comment, + description, + entries, + itemId, + listId, + meta, + name, + namespaceType, + tags, + type, + }: CreateExceptionListItemOptions): Promise => { + const { savedObjectsClient, user } = this; + return createExceptionListItem({ + _tags, + comment, + description, + entries, + itemId, + listId, + meta, + name, + namespaceType, + savedObjectsClient, + tags, + type, + user, + }); + }; + + public updateExceptionListItem = async ({ + _tags, + comment, + description, + entries, + id, + itemId, + meta, + name, + namespaceType, + tags, + type, + }: UpdateExceptionListItemOptions): Promise => { + const { savedObjectsClient, user } = this; + return updateExceptionListItem({ + _tags, + comment, + description, + entries, + id, + itemId, + meta, + name, + namespaceType, + savedObjectsClient, + tags, + type, + user, + }); + }; + + public deleteExceptionListItem = async ({ + id, + itemId, + namespaceType, + }: DeleteExceptionListItemOptions): Promise => { + const { savedObjectsClient } = this; + return deleteExceptionListItem({ + id, + itemId, + namespaceType, + savedObjectsClient, + }); + }; + + public findExceptionListItem = async ({ + listId, + filter, + perPage, + page, + sortField, + sortOrder, + namespaceType, + }: FindExceptionListItemOptions): Promise => { + const { savedObjectsClient } = this; + return findExceptionListItem({ + filter, + listId, + namespaceType, + page, + perPage, + savedObjectsClient, + sortField, + sortOrder, + }); + }; + + public findExceptionList = async ({ + filter, + perPage, + page, + sortField, + sortOrder, + namespaceType, + }: FindExceptionListOptions): Promise => { + const { savedObjectsClient } = this; + return findExceptionList({ + filter, + namespaceType, + page, + perPage, + savedObjectsClient, + sortField, + sortOrder, + }); + }; +} diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts new file mode 100644 index 0000000000000..cecd6bf3397a7 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; + +import { + CommentOrUndefined, + Description, + DescriptionOrUndefined, + EntriesArray, + EntriesArrayOrUndefined, + ExceptionListType, + ExceptionListTypeOrUndefined, + IdOrUndefined, + ItemId, + ItemIdOrUndefined, + ListId, + ListIdOrUndefined, + MetaOrUndefined, + Name, + NameOrUndefined, + Tags, + TagsOrUndefined, + _Tags, + _TagsOrUndefined, +} from '../../../common/schemas'; + +import { NamespaceType } from './types'; + +export interface ConstructorOptions { + user: string; + savedObjectsClient: SavedObjectsClientContract; +} + +export interface GetExceptionListOptions { + listId: ListIdOrUndefined; + id: IdOrUndefined; + namespaceType: NamespaceType; +} + +export interface CreateExceptionListOptions { + _tags: _Tags; + listId: ListId; + namespaceType: NamespaceType; + name: Name; + description: Description; + meta: MetaOrUndefined; + tags: Tags; + type: ExceptionListType; +} + +export interface UpdateExceptionListOptions { + _tags: _TagsOrUndefined; + id: IdOrUndefined; + listId: ListIdOrUndefined; + namespaceType: NamespaceType; + name: NameOrUndefined; + description: DescriptionOrUndefined; + meta: MetaOrUndefined; + tags: TagsOrUndefined; + type: ExceptionListTypeOrUndefined; +} + +export interface DeleteExceptionListOptions { + id: IdOrUndefined; + listId: ListIdOrUndefined; + namespaceType: NamespaceType; +} + +export interface DeleteExceptionListItemOptions { + id: IdOrUndefined; + itemId: ItemIdOrUndefined; + namespaceType: NamespaceType; +} + +export interface GetExceptionListItemOptions { + itemId: ItemIdOrUndefined; + id: IdOrUndefined; + namespaceType: NamespaceType; +} + +export interface CreateExceptionListItemOptions { + _tags: _Tags; + comment: CommentOrUndefined; + entries: EntriesArray; + itemId: ItemId; + listId: ListId; + namespaceType: NamespaceType; + name: Name; + description: Description; + meta: MetaOrUndefined; + tags: Tags; + type: ExceptionListType; +} + +export interface UpdateExceptionListItemOptions { + _tags: _TagsOrUndefined; + comment: CommentOrUndefined; + entries: EntriesArrayOrUndefined; + id: IdOrUndefined; + itemId: ItemIdOrUndefined; + namespaceType: NamespaceType; + name: NameOrUndefined; + description: DescriptionOrUndefined; + meta: MetaOrUndefined; + tags: TagsOrUndefined; + type: ExceptionListTypeOrUndefined; +} + +export interface FindExceptionListItemOptions { + listId: ListId; + namespaceType: NamespaceType; + filter: string | undefined; + perPage: number | undefined; + page: number | undefined; + sortField: string | undefined; + sortOrder: string | undefined; +} + +export interface FindExceptionListOptions { + namespaceType: NamespaceType; + filter: string | undefined; + perPage: number | undefined; + page: number | undefined; + sortField: string | undefined; + sortOrder: string | undefined; +} diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts new file mode 100644 index 0000000000000..539dda673208b --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; + +import { ExceptionListSoSchema, FoundExceptionListSchema } from '../../../common/schemas'; +import { SavedObjectType } from '../../saved_objects'; + +import { getSavedObjectType, transformSavedObjectsToFounExceptionList } from './utils'; +import { NamespaceType } from './types'; + +interface FindExceptionListOptions { + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; + filter: string | undefined; + perPage: number | undefined; + page: number | undefined; + sortField: string | undefined; + sortOrder: string | undefined; +} + +export const findExceptionList = async ({ + namespaceType, + savedObjectsClient, + filter, + page, + perPage, + sortField, + sortOrder, +}: FindExceptionListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const savedObjectsFindResponse = await savedObjectsClient.find({ + filter: getExceptionListFilter({ filter, savedObjectType }), + page, + perPage, + sortField, + sortOrder, + type: savedObjectType, + }); + return transformSavedObjectsToFounExceptionList({ savedObjectsFindResponse }); +}; + +export const getExceptionListFilter = ({ + filter, + savedObjectType, +}: { + filter: string | undefined; + savedObjectType: SavedObjectType; +}): string => { + if (filter == null) { + return `${savedObjectType}.attributes.list_type: list`; + } else { + return `${savedObjectType}.attributes.list_type: list AND ${filter}`; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts new file mode 100644 index 0000000000000..d635cafbd3b1b --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; + +import { + ExceptionListSoSchema, + FoundExceptionListItemSchema, + ListId, +} from '../../../common/schemas'; +import { SavedObjectType } from '../../saved_objects'; + +import { getSavedObjectType, transformSavedObjectsToFounExceptionListItem } from './utils'; +import { NamespaceType } from './types'; +import { getExceptionList } from './get_exception_list'; + +interface FindExceptionListItemOptions { + listId: ListId; + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; + filter: string | undefined; + perPage: number | undefined; + page: number | undefined; + sortField: string | undefined; + sortOrder: string | undefined; +} + +export const findExceptionListItem = async ({ + listId, + namespaceType, + savedObjectsClient, + filter, + page, + perPage, + sortField, + sortOrder, +}: FindExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionList = await getExceptionList({ + id: undefined, + listId, + namespaceType, + savedObjectsClient, + }); + if (exceptionList == null) { + return null; + } else { + const savedObjectsFindResponse = await savedObjectsClient.find({ + filter: getExceptionListItemFilter({ filter, listId, savedObjectType }), + page, + perPage, + sortField, + sortOrder, + type: savedObjectType, + }); + return transformSavedObjectsToFounExceptionListItem({ savedObjectsFindResponse }); + } +}; + +export const getExceptionListItemFilter = ({ + filter, + listId, + savedObjectType, +}: { + listId: ListId; + filter: string | undefined; + savedObjectType: SavedObjectType; +}): string => { + if (filter == null) { + return `${savedObjectType}.attributes.list_type: item AND ${savedObjectType}.attributes.list_id: ${listId}`; + } else { + return `${savedObjectType}.attributes.list_type: item AND ${savedObjectType}.attributes.list_id: ${listId} AND ${filter}`; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts new file mode 100644 index 0000000000000..8b28443b4e30c --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + SavedObjectsClientContract, + SavedObjectsErrorHelpers, +} from '../../../../../../src/core/server/'; +import { + ExceptionListSchema, + ExceptionListSoSchema, + IdOrUndefined, + ListIdOrUndefined, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils'; +import { NamespaceType } from './types'; + +interface GetExceptionListOptions { + id: IdOrUndefined; + listId: ListIdOrUndefined; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; +} + +export const getExceptionList = async ({ + id, + listId, + savedObjectsClient, + namespaceType, +}: GetExceptionListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + if (id != null) { + try { + const savedObject = await savedObjectsClient.get(savedObjectType, id); + return transformSavedObjectToExceptionList({ savedObject }); + } catch (err) { + if (SavedObjectsErrorHelpers.isNotFoundError(err)) { + return null; + } else { + throw err; + } + } + } else if (listId != null) { + const savedObject = await savedObjectsClient.find({ + filter: `${savedObjectType}.attributes.list_type: list`, + perPage: 1, + search: listId, + searchFields: ['list_id'], + sortField: 'tie_breaker_id', + sortOrder: 'desc', + type: savedObjectType, + }); + if (savedObject.saved_objects[0] != null) { + return transformSavedObjectToExceptionList({ savedObject: savedObject.saved_objects[0] }); + } else { + return null; + } + } else { + return null; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.ts new file mode 100644 index 0000000000000..7ef3e4af3d604 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.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 { + SavedObjectsClientContract, + SavedObjectsErrorHelpers, +} from '../../../../../../src/core/server/'; +import { + ExceptionListItemSchema, + ExceptionListSoSchema, + IdOrUndefined, + ItemIdOrUndefined, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectToExceptionListItem } from './utils'; +import { NamespaceType } from './types'; + +interface GetExceptionListItemOptions { + id: IdOrUndefined; + itemId: ItemIdOrUndefined; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; +} + +export const getExceptionListItem = async ({ + id, + itemId, + savedObjectsClient, + namespaceType, +}: GetExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + if (id != null) { + try { + const savedObject = await savedObjectsClient.get(savedObjectType, id); + return transformSavedObjectToExceptionListItem({ savedObject }); + } catch (err) { + if (SavedObjectsErrorHelpers.isNotFoundError(err)) { + return null; + } else { + throw err; + } + } + } else if (itemId != null) { + const savedObject = await savedObjectsClient.find({ + filter: `${savedObjectType}.attributes.list_type: item`, + perPage: 1, + search: itemId, + searchFields: ['item_id'], + sortField: 'tie_breaker_id', + sortOrder: 'desc', + type: savedObjectType, + }); + if (savedObject.saved_objects[0] != null) { + return transformSavedObjectToExceptionListItem({ + savedObject: savedObject.saved_objects[0], + }); + } else { + return null; + } + } else { + return null; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/index.ts b/x-pack/plugins/lists/server/services/exception_lists/index.ts new file mode 100644 index 0000000000000..a66f00819605b --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './create_exception_list_item'; +export * from './create_exception_list'; +export * from './delete_exception_list_item'; +export * from './delete_exception_list'; +export * from './find_exception_list'; +export * from './find_exception_list_item'; +export * from './get_exception_list_item'; +export * from './get_exception_list'; +export * from './update_exception_list_item'; +export * from './update_exception_list'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/types.ts b/x-pack/plugins/lists/server/services/exception_lists/types.ts new file mode 100644 index 0000000000000..dbb188bc2754a --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/types.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export type NamespaceType = 'agnostic' | 'single'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts new file mode 100644 index 0000000000000..6c5ccb5e1f2fd --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; + +import { + DescriptionOrUndefined, + ExceptionListSchema, + ExceptionListSoSchema, + ExceptionListTypeOrUndefined, + IdOrUndefined, + ListIdOrUndefined, + MetaOrUndefined, + NameOrUndefined, + TagsOrUndefined, + _TagsOrUndefined, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectUpdateToExceptionList } from './utils'; +import { NamespaceType } from './types'; +import { getExceptionList } from './get_exception_list'; + +interface UpdateExceptionListOptions { + id: IdOrUndefined; + _tags: _TagsOrUndefined; + name: NameOrUndefined; + description: DescriptionOrUndefined; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; + listId: ListIdOrUndefined; + meta: MetaOrUndefined; + user: string; + tags: TagsOrUndefined; + tieBreaker?: string; + type: ExceptionListTypeOrUndefined; +} + +export const updateExceptionList = async ({ + _tags, + id, + savedObjectsClient, + namespaceType, + name, + description, + listId, + meta, + user, + tags, + type, +}: UpdateExceptionListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionList = await getExceptionList({ id, listId, namespaceType, savedObjectsClient }); + if (exceptionList == null) { + return null; + } else { + const savedObject = await savedObjectsClient.update( + savedObjectType, + exceptionList.id, + { + _tags, + description, + meta, + name, + tags, + type, + updated_by: user, + } + ); + return transformSavedObjectUpdateToExceptionList({ exceptionList, savedObject }); + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts new file mode 100644 index 0000000000000..4e955d4281c4d --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; + +import { + CommentOrUndefined, + DescriptionOrUndefined, + EntriesArrayOrUndefined, + ExceptionListItemSchema, + ExceptionListSoSchema, + ExceptionListTypeOrUndefined, + IdOrUndefined, + ItemIdOrUndefined, + MetaOrUndefined, + NameOrUndefined, + TagsOrUndefined, + _TagsOrUndefined, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectUpdateToExceptionListItem } from './utils'; +import { NamespaceType } from './types'; +import { getExceptionListItem } from './get_exception_list_item'; + +interface UpdateExceptionListItemOptions { + id: IdOrUndefined; + comment: CommentOrUndefined; + _tags: _TagsOrUndefined; + name: NameOrUndefined; + description: DescriptionOrUndefined; + entries: EntriesArrayOrUndefined; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; + itemId: ItemIdOrUndefined; + meta: MetaOrUndefined; + user: string; + tags: TagsOrUndefined; + tieBreaker?: string; + type: ExceptionListTypeOrUndefined; +} + +export const updateExceptionListItem = async ({ + _tags, + comment, + entries, + id, + savedObjectsClient, + namespaceType, + name, + description, + itemId, + meta, + user, + tags, + type, +}: UpdateExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionListItem = await getExceptionListItem({ + id, + itemId, + namespaceType, + savedObjectsClient, + }); + if (exceptionListItem == null) { + return null; + } else { + const savedObject = await savedObjectsClient.update( + savedObjectType, + exceptionListItem.id, + { + _tags, + comment, + description, + entries, + meta, + name, + tags, + type, + updated_by: user, + } + ); + return transformSavedObjectUpdateToExceptionListItem({ exceptionListItem, savedObject }); + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils.ts b/x-pack/plugins/lists/server/services/exception_lists/utils.ts new file mode 100644 index 0000000000000..505ebc43afc5a --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/utils.ts @@ -0,0 +1,235 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObject, SavedObjectsFindResponse, SavedObjectsUpdateResponse } from 'kibana/server'; + +import { + ExceptionListItemSchema, + ExceptionListSchema, + ExceptionListSoSchema, + FoundExceptionListItemSchema, + FoundExceptionListSchema, +} from '../../../common/schemas'; +import { + SavedObjectType, + exceptionListAgnosticSavedObjectType, + exceptionListSavedObjectType, +} from '../../saved_objects'; + +import { NamespaceType } from './types'; + +export const getSavedObjectType = ({ + namespaceType, +}: { + namespaceType: NamespaceType; +}): SavedObjectType => { + if (namespaceType === 'agnostic') { + return exceptionListAgnosticSavedObjectType; + } else { + return exceptionListSavedObjectType; + } +}; + +export const transformSavedObjectToExceptionList = ({ + savedObject, +}: { + savedObject: SavedObject; +}): ExceptionListSchema => { + const dateNow = new Date().toISOString(); + const { + attributes: { + _tags, + created_at, + created_by, + description, + list_id, + meta, + name, + tags, + tie_breaker_id, + type, + updated_by, + }, + id, + updated_at: updatedAt, + } = savedObject; + + // TODO: Change this to do a decode and throw if the saved object is not as expected. + // TODO: Do a throw if after the decode this is not the correct "list_type: list" + return { + _tags, + created_at, + created_by, + description, + id, + list_id, + meta, + name, + tags, + tie_breaker_id, + type, + updated_at: updatedAt ?? dateNow, + updated_by, + }; +}; + +export const transformSavedObjectUpdateToExceptionList = ({ + exceptionList, + savedObject, +}: { + exceptionList: ExceptionListSchema; + savedObject: SavedObjectsUpdateResponse; +}): ExceptionListSchema => { + const dateNow = new Date().toISOString(); + const { + attributes: { _tags, description, meta, name, tags, type, updated_by: updatedBy }, + id, + updated_at: updatedAt, + } = savedObject; + + // TODO: Change this to do a decode and throw if the saved object is not as expected. + // TODO: Do a throw if after the decode this is not the correct "list_type: list" + return { + _tags: _tags ?? exceptionList._tags, + created_at: exceptionList.created_at, + created_by: exceptionList.created_by, + description: description ?? exceptionList.description, + id, + list_id: exceptionList.list_id, + meta: meta ?? exceptionList.meta, + name: name ?? exceptionList.name, + tags: tags ?? exceptionList.tags, + tie_breaker_id: exceptionList.tie_breaker_id, + type: type ?? exceptionList.type, + updated_at: updatedAt ?? dateNow, + updated_by: updatedBy ?? exceptionList.updated_by, + }; +}; + +export const transformSavedObjectToExceptionListItem = ({ + savedObject, +}: { + savedObject: SavedObject; +}): ExceptionListItemSchema => { + const dateNow = new Date().toISOString(); + const { + attributes: { + _tags, + comment, + created_at, + created_by, + description, + entries, + item_id: itemId, + list_id, + meta, + name, + tags, + tie_breaker_id, + type, + updated_by, + }, + id, + updated_at: updatedAt, + } = savedObject; + // TODO: Change this to do a decode and throw if the saved object is not as expected. + // TODO: Do a throw if after the decode this is not the correct "list_type: item" + // TODO: Do a throw if item_id or entries is not defined. + return { + _tags, + comment, + created_at, + created_by, + description, + entries: entries ?? [], + id, + item_id: itemId ?? '(unknown)', + list_id, + meta, + name, + tags, + tie_breaker_id, + type, + updated_at: updatedAt ?? dateNow, + updated_by, + }; +}; + +export const transformSavedObjectUpdateToExceptionListItem = ({ + exceptionListItem, + savedObject, +}: { + exceptionListItem: ExceptionListItemSchema; + savedObject: SavedObjectsUpdateResponse; +}): ExceptionListItemSchema => { + const dateNow = new Date().toISOString(); + const { + attributes: { + _tags, + comment, + description, + entries, + meta, + name, + tags, + type, + updated_by: updatedBy, + }, + id, + updated_at: updatedAt, + } = savedObject; + + // TODO: Change this to do a decode and throw if the saved object is not as expected. + // TODO: Do a throw if after the decode this is not the correct "list_type: list" + return { + _tags: _tags ?? exceptionListItem._tags, + comment: comment ?? exceptionListItem.comment, + created_at: exceptionListItem.created_at, + created_by: exceptionListItem.created_by, + description: description ?? exceptionListItem.description, + entries: entries ?? exceptionListItem.entries, + id, + item_id: exceptionListItem.item_id, + list_id: exceptionListItem.list_id, + meta: meta ?? exceptionListItem.meta, + name: name ?? exceptionListItem.name, + tags: tags ?? exceptionListItem.tags, + tie_breaker_id: exceptionListItem.tie_breaker_id, + type: type ?? exceptionListItem.type, + updated_at: updatedAt ?? dateNow, + updated_by: updatedBy ?? exceptionListItem.updated_by, + }; +}; + +export const transformSavedObjectsToFounExceptionListItem = ({ + savedObjectsFindResponse, +}: { + savedObjectsFindResponse: SavedObjectsFindResponse; +}): FoundExceptionListItemSchema => { + return { + data: savedObjectsFindResponse.saved_objects.map(savedObject => + transformSavedObjectToExceptionListItem({ savedObject }) + ), + page: savedObjectsFindResponse.page, + per_page: savedObjectsFindResponse.per_page, + total: savedObjectsFindResponse.total, + }; +}; + +export const transformSavedObjectsToFounExceptionList = ({ + savedObjectsFindResponse, +}: { + savedObjectsFindResponse: SavedObjectsFindResponse; +}): FoundExceptionListSchema => { + return { + data: savedObjectsFindResponse.saved_objects.map(savedObject => + transformSavedObjectToExceptionList({ savedObject }) + ), + page: savedObjectsFindResponse.page, + per_page: savedObjectsFindResponse.per_page, + total: savedObjectsFindResponse.total, + }; +}; diff --git a/x-pack/plugins/lists/server/services/lists/client.ts b/x-pack/plugins/lists/server/services/lists/list_client.ts similarity index 99% rename from x-pack/plugins/lists/server/services/lists/client.ts rename to x-pack/plugins/lists/server/services/lists/list_client.ts index ba22bf72cc18c..cba48115c746c 100644 --- a/x-pack/plugins/lists/server/services/lists/client.ts +++ b/x-pack/plugins/lists/server/services/lists/list_client.ts @@ -59,7 +59,7 @@ import { ImportListItemsToStreamOptions, UpdateListItemOptions, UpdateListOptions, -} from './client_types'; +} from './list_client_types'; export class ListClient { private readonly spaceId: string; diff --git a/x-pack/plugins/lists/server/services/lists/client_types.ts b/x-pack/plugins/lists/server/services/lists/list_client_types.ts similarity index 100% rename from x-pack/plugins/lists/server/services/lists/client_types.ts rename to x-pack/plugins/lists/server/services/lists/list_client_types.ts diff --git a/x-pack/plugins/lists/server/services/utils/index.ts b/x-pack/plugins/lists/server/services/utils/index.ts index 8a44b5ab607bf..e6365e689f761 100644 --- a/x-pack/plugins/lists/server/services/utils/index.ts +++ b/x-pack/plugins/lists/server/services/utils/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './derive_type_from_es_type'; export * from './get_query_filter_from_type_value'; export * from './transform_elastic_to_list_item'; export * from './transform_list_item_to_elastic_query'; -export * from './derive_type_from_es_type'; diff --git a/x-pack/plugins/lists/server/siem_server_deps.ts b/x-pack/plugins/lists/server/siem_server_deps.ts index 6231b2e014111..81ebe9f17b06f 100644 --- a/x-pack/plugins/lists/server/siem_server_deps.ts +++ b/x-pack/plugins/lists/server/siem_server_deps.ts @@ -6,27 +6,16 @@ export { transformError, + deleteTemplate, + deletePolicy, + deleteAllIndex, + setPolicy, + setTemplate, buildSiemResponse, -} from '../../siem/server/lib/detection_engine/routes/utils'; // eslint-disable-line @kbn/eslint/no-restricted-paths -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { deleteTemplate } from '../../siem/server/lib/detection_engine/index/delete_template'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { deletePolicy } from '../../siem/server/lib/detection_engine/index/delete_policy'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { deleteAllIndex } from '../../siem/server/lib/detection_engine/index/delete_all_index'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { setPolicy } from '../../siem/server/lib/detection_engine/index/set_policy'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { setTemplate } from '../../siem/server/lib/detection_engine/index/set_template'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { getTemplateExists } from '../../siem/server/lib/detection_engine/index/get_template_exists'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { getPolicyExists } from '../../siem/server/lib/detection_engine/index/get_policy_exists'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { createBootstrapIndex } from '../../siem/server/lib/detection_engine/index/create_bootstrap_index'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { getIndexExists } from '../../siem/server/lib/detection_engine/index/get_index_exists'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { buildRouteValidation } from '../../siem/server/utils/build_validation/route_validation'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { validate } from '../../siem/server/lib/detection_engine/routes/rules/validate'; + getTemplateExists, + getPolicyExists, + createBootstrapIndex, + getIndexExists, + buildRouteValidation, + validate, +} from '../../siem/server'; diff --git a/x-pack/plugins/lists/server/types.ts b/x-pack/plugins/lists/server/types.ts index d7c3208e556fa..6808aaa04ab7a 100644 --- a/x-pack/plugins/lists/server/types.ts +++ b/x-pack/plugins/lists/server/types.ts @@ -4,12 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { APICaller, IContextProvider, RequestHandler } from 'kibana/server'; +import { + APICaller, + IContextProvider, + RequestHandler, + SavedObjectsClientContract, +} from 'kibana/server'; import { SecurityPluginSetup } from '../../security/server'; import { SpacesPluginSetup } from '../../spaces/server'; -import { ListClient } from './services/lists/client'; +import { ListClient } from './services/lists/list_client'; +import { ExceptionListClient } from './services/exception_lists/exception_list_client'; export type ContextProvider = IContextProvider, 'lists'>; export type ListsPluginStart = void; @@ -23,14 +29,25 @@ export type GetListClientType = ( spaceId: string, user: string ) => ListClient; + +export type GetExceptionListClientType = ( + savedObjectsClient: SavedObjectsClientContract, + user: string +) => ExceptionListClient; + export interface ListPluginSetup { + getExceptionListClient: GetExceptionListClientType; getListClient: GetListClientType; } -export type ContextProviderReturn = Promise<{ getListClient: () => ListClient }>; +export type ContextProviderReturn = Promise<{ + getListClient: () => ListClient; + getExceptionListClient: () => ExceptionListClient; +}>; declare module 'src/core/server' { interface RequestHandlerContext { lists?: { + getExceptionListClient: () => ExceptionListClient; getListClient: () => ListClient; }; } diff --git a/x-pack/plugins/siem/server/utils/build_validation/exact_check.test.ts b/x-pack/plugins/siem/common/exact_check.test.ts similarity index 98% rename from x-pack/plugins/siem/server/utils/build_validation/exact_check.test.ts rename to x-pack/plugins/siem/common/exact_check.test.ts index 1e70deaeed438..a5701603de0d6 100644 --- a/x-pack/plugins/siem/server/utils/build_validation/exact_check.test.ts +++ b/x-pack/plugins/siem/common/exact_check.test.ts @@ -8,8 +8,8 @@ import * as t from 'io-ts'; import { left, right, Either } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { foldLeftRight, getPaths } from './__mocks__/utils'; import { exactCheck, findDifferencesRecursive } from './exact_check'; +import { foldLeftRight, getPaths } from './test_utils'; describe('exact_check', () => { test('it returns an error if given extra object properties', () => { diff --git a/x-pack/plugins/siem/server/utils/build_validation/exact_check.ts b/x-pack/plugins/siem/common/exact_check.ts similarity index 100% rename from x-pack/plugins/siem/server/utils/build_validation/exact_check.ts rename to x-pack/plugins/siem/common/exact_check.ts diff --git a/x-pack/plugins/siem/server/utils/build_validation/format_errors.test.ts b/x-pack/plugins/siem/common/format_errors.test.ts similarity index 100% rename from x-pack/plugins/siem/server/utils/build_validation/format_errors.test.ts rename to x-pack/plugins/siem/common/format_errors.test.ts diff --git a/x-pack/plugins/siem/server/utils/build_validation/format_errors.ts b/x-pack/plugins/siem/common/format_errors.ts similarity index 100% rename from x-pack/plugins/siem/server/utils/build_validation/format_errors.ts rename to x-pack/plugins/siem/common/format_errors.ts diff --git a/x-pack/plugins/siem/server/utils/build_validation/__mocks__/utils.ts b/x-pack/plugins/siem/common/test_utils.ts similarity index 95% rename from x-pack/plugins/siem/server/utils/build_validation/__mocks__/utils.ts rename to x-pack/plugins/siem/common/test_utils.ts index 578972dda5aef..29b0119dcbeb7 100644 --- a/x-pack/plugins/siem/server/utils/build_validation/__mocks__/utils.ts +++ b/x-pack/plugins/siem/common/test_utils.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { formatErrors } from '../format_errors'; +import { formatErrors } from './format_errors'; interface Message { errors: t.Errors; diff --git a/x-pack/plugins/siem/server/index.ts b/x-pack/plugins/siem/server/index.ts index e9cd78589fac9..586b9dec2f4ab 100644 --- a/x-pack/plugins/siem/server/index.ts +++ b/x-pack/plugins/siem/server/index.ts @@ -15,3 +15,17 @@ export const plugin = (context: PluginInitializerContext) => { export const config = { schema: configSchema }; export { ConfigType, Plugin, PluginSetup, PluginStart }; + +// Exports to be shared with plugins such as x-pack/lists plugin +export { deleteTemplate } from './lib/detection_engine/index/delete_template'; +export { deletePolicy } from './lib/detection_engine/index/delete_policy'; +export { deleteAllIndex } from './lib/detection_engine/index/delete_all_index'; +export { setPolicy } from './lib/detection_engine/index/set_policy'; +export { setTemplate } from './lib/detection_engine/index/set_template'; +export { getTemplateExists } from './lib/detection_engine/index/get_template_exists'; +export { getPolicyExists } from './lib/detection_engine/index/get_policy_exists'; +export { createBootstrapIndex } from './lib/detection_engine/index/create_bootstrap_index'; +export { getIndexExists } from './lib/detection_engine/index/get_index_exists'; +export { buildRouteValidation } from './utils/build_validation/route_validation'; +export { validate } from './lib/detection_engine/routes/rules/validate'; +export { transformError, buildSiemResponse } from './lib/detection_engine/routes/utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts index cfba40fc225a2..cda3a4b81ed9b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts @@ -9,8 +9,8 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import * as t from 'io-ts'; -import { formatErrors } from '../../../../utils/build_validation/format_errors'; -import { exactCheck } from '../../../../utils/build_validation/exact_check'; +import { formatErrors } from '../../../../../common/format_errors'; +import { exactCheck } from '../../../../../common/exact_check'; import { PartialAlert, FindResult } from '../../../../../../alerting/server'; import { isAlertType, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts index fbd2382e2826d..0b0d3bf43b1e9 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts @@ -20,8 +20,8 @@ import { left } from 'fp-ts/lib/Either'; import { RulesSchema } from './rules_schema'; import { TypeAndTimelineOnly } from './type_timeline_only_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('check_type_dependents', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts index 6e159a792edb6..9bbde3d5236db 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts @@ -10,8 +10,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { getErrorPayload } from './__mocks__/utils'; import { errorSchema, ErrorSchema } from './error_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('error_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts index 68b67db595d76..1b7d7994462c7 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts @@ -10,8 +10,8 @@ import { getFindResponseSingle, getBaseResponsePayload } from './__mocks__/utils import { left } from 'fp-ts/lib/Either'; import { RulesSchema } from './rules_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { getPaths, foldLeftRight } from '../../../../../utils/build_validation/__mocks__/utils'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../../../../common/exact_check'; describe('find_rules_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts index b0b863ebbbc0b..18e17a319883a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts @@ -9,9 +9,9 @@ import { left, Either } from 'fp-ts/lib/Either'; import { ImportRulesSchema, importRulesSchema } from './import_rules_schema'; import { ErrorSchema } from './error_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { getPaths, foldLeftRight } from '../../../../../utils/build_validation/__mocks__/utils'; import { Errors } from 'io-ts'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('import_rules_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts index 827167c63fd58..2d3fd75914822 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts @@ -8,8 +8,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; import { PrePackagedRulesSchema, prePackagedRulesSchema } from './prepackaged_rules_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('prepackaged_rules_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts index a864667583c0a..abe601a546111 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts @@ -11,8 +11,8 @@ import { prePackagedRulesStatusSchema, } from './prepackaged_rules_status_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('prepackaged_rules_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts index 9a7cf5e2c2871..98cb2ef058485 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts @@ -12,8 +12,8 @@ import { RulesBulkSchema, rulesBulkSchema } from './rules_bulk_schema'; import { RulesSchema } from './rules_schema'; import { ErrorSchema } from './error_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('prepackaged_rule_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts index 82a6682e6461f..ade4d12517aca 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts @@ -10,8 +10,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { rulesSchema, RulesSchema, removeList } from './rules_schema'; import { getBaseResponsePayload } from './__mocks__/utils'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../../../../common/exact_check'; export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts index 85fb124464487..8f06e2c6e49b0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts @@ -9,8 +9,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { TypeAndTimelineOnly, typeAndTimelineOnlySchema } from './type_timeline_only_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('prepackaged_rule_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts index ff62ea4443f3f..9f9181359d44a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts @@ -7,7 +7,7 @@ import { IsoDateString } from './iso_date_string'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('ios_date_string', () => { test('it should validate a iso string', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts index 2a97c8a4a143e..dc0bd6cacf0d6 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts @@ -7,7 +7,7 @@ import { ListsDefaultArray } from './lists_default_array'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('lists_default_array', () => { test('it should validate an empty array', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts index d6f21681df88f..a3338c878bd71 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts @@ -7,7 +7,7 @@ import { PositiveIntegerGreaterThanZero } from './positive_integer_greater_than_zero'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('positive_integer_greater_than_zero', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts index 26441745a7f29..48ea2025b9b12 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts @@ -7,7 +7,7 @@ import { PositiveInteger } from './positive_integer'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('positive_integer_greater_than_zero', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts index 76f722274ce23..3aaff7e00ad51 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts @@ -7,7 +7,7 @@ import { ReferencesDefaultArray } from './references_default_array'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('references_default_array', () => { test('it should validate an empty array', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts index 76e3445358dd9..41c0faf4d608d 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts @@ -7,7 +7,7 @@ import { RiskScore } from './risk_score'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('risk_score', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts index 7b68dbcef2d7e..b640b449e6b8a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts @@ -7,7 +7,7 @@ import { UUID } from './uuid'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('uuid', () => { test('it should validate a uuid', () => { diff --git a/x-pack/plugins/siem/server/utils/build_validation/route_validation.ts b/x-pack/plugins/siem/server/utils/build_validation/route_validation.ts index 30b95dcfa94ee..19353080c15cb 100644 --- a/x-pack/plugins/siem/server/utils/build_validation/route_validation.ts +++ b/x-pack/plugins/siem/server/utils/build_validation/route_validation.ts @@ -7,13 +7,13 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import * as rt from 'io-ts'; +import { formatErrors } from '../../../common/format_errors'; +import { exactCheck } from '../../../common/exact_check'; import { RouteValidationFunction, RouteValidationResultFactory, RouteValidationError, } from '../../../../../../src/core/server'; -import { exactCheck } from './exact_check'; -import { formatErrors } from './format_errors'; type RequestValidationResult = | { From 8dc8aa0c64317e0987849f37d4f83cd324d12922 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 15 May 2020 14:18:39 -0600 Subject: [PATCH 03/18] [Maps] Handle cross cluster index _settings resp (#66797) --- .../plugins/maps/server/lib/get_index_pattern_settings.js | 6 +++++- .../maps/server/lib/get_index_pattern_settings.test.js | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.js b/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.js index e2a758075155a..b7cd1687dc257 100644 --- a/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.js +++ b/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.js @@ -29,5 +29,9 @@ export function getIndexPatternSettings(indicesSettingsResp) { maxInnerResultWindow = Math.min(indexMaxInnerResultWindow, indexMaxResultWindow); }); - return { maxResultWindow, maxInnerResultWindow }; + return { + maxResultWindow: maxResultWindow === Infinity ? DEFAULT_MAX_RESULT_WINDOW : maxResultWindow, + maxInnerResultWindow: + maxInnerResultWindow === Infinity ? DEFAULT_MAX_INNER_RESULT_WINDOW : maxInnerResultWindow, + }; } diff --git a/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.test.js b/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.test.js index c152f5bfffc31..46949a2e3e2cf 100644 --- a/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.test.js +++ b/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.test.js @@ -24,6 +24,14 @@ describe('max_result_window and max_inner_result_window are not set', () => { expect(maxInnerResultWindow).toBe(DEFAULT_MAX_INNER_RESULT_WINDOW); }); + test('Should provide default values from cross cluster index response', () => { + // _settings returns empty object for cross cluster index + const indicesSettingsResp = {}; + const { maxResultWindow, maxInnerResultWindow } = getIndexPatternSettings(indicesSettingsResp); + expect(maxResultWindow).toBe(DEFAULT_MAX_RESULT_WINDOW); + expect(maxInnerResultWindow).toBe(DEFAULT_MAX_INNER_RESULT_WINDOW); + }); + test('Should include default values when providing minimum values for indices in index pattern', () => { const indicesSettingsResp = { kibana_sample_data_logs: { From a8e82118c8742a444cfcb751e57571db64955025 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Fri, 15 May 2020 21:32:27 +0100 Subject: [PATCH 04/18] chore(NA): bump static-fs to 1.0.2 (#66775) Co-authored-by: Elastic Machine --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 91034fea5156a..72a63ec34d2c7 100644 --- a/package.json +++ b/package.json @@ -298,7 +298,7 @@ "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^5.0.1", - "@elastic/static-fs": "1.0.1", + "@elastic/static-fs": "1.0.2", "@kbn/dev-utils": "1.0.0", "@kbn/es": "1.0.0", "@kbn/eslint-import-resolver-kibana": "2.0.0", diff --git a/yarn.lock b/yarn.lock index 245e0de152658..a18f89cd480f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1427,10 +1427,10 @@ "@types/node-jose" "1.1.0" node-jose "1.1.0" -"@elastic/static-fs@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@elastic/static-fs/-/static-fs-1.0.1.tgz#2e084e9fc321dd4c7fb4579021ca8a6b26f3464e" - integrity sha512-Vl40Va/h0P6aDUrzgDeTabGVUb/s/W92le64E1UXTcG5927cZtTnOu0datMjr98xdr9C6RAJ3mr6zgxfox5TNw== +"@elastic/static-fs@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@elastic/static-fs/-/static-fs-1.0.2.tgz#c1e5fea6a1b35abcd005cecf7880156ed0f273ae" + integrity sha512-0cZc5D9Wg6pJsc8Sa2ns1eOuxtXEidE7GBb2B0KZdJq9nZzUCxMyplURqT0Nr3i5XpoHb6ZEmxWsji86j1KjDw== "@elastic/ui-ace@0.2.3": version "0.2.3" From 3228f1d1371da58865ce2c6ff486d3a15d3c4e85 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 15 May 2020 15:59:59 -0600 Subject: [PATCH 05/18] [SIEM] Fixes glob patterns from directory changes recently for GraphQL ## Summary Our directory structure has changed recently and the GraphQL needs to be updated for it to find the new location of the graphql files. I used a more global glob approach which will make it easier to change directories again and not have it break. This does cause the generated file to have different positions and looks like changes but they are just movements. To test this: * Check it out * Run this: ```sh cd $HOME/projects/kibana/x-pack/plugins/siem && node scripts/generate_types_from_graphql.js ``` And ensure the types do not change and you can run a type check like so: ```sh cd $HOME/projects/kibana && node scripts/type_check.js --project x-pack/tsconfig.json ``` --- x-pack/plugins/siem/public/graphql/types.ts | 1194 ++++++++--------- .../scripts/generate_types_from_graphql.js | 2 +- 2 files changed, 598 insertions(+), 598 deletions(-) diff --git a/x-pack/plugins/siem/public/graphql/types.ts b/x-pack/plugins/siem/public/graphql/types.ts index 3436ee84a2f30..c3493c580fa22 100644 --- a/x-pack/plugins/siem/public/graphql/types.ts +++ b/x-pack/plugins/siem/public/graphql/types.ts @@ -2541,6 +2541,140 @@ export interface DeleteTimelineMutationArgs { // Documents // ==================================================== +export namespace GetLastEventTimeQuery { + export type Variables = { + sourceId: string; + indexKey: LastEventIndexKey; + details: LastTimeDetails; + defaultIndex: string[]; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + LastEventTime: LastEventTime; + }; + + export type LastEventTime = { + __typename?: 'LastEventTimeData'; + + lastSeen: Maybe; + }; +} + +export namespace GetMatrixHistogramQuery { + export type Variables = { + defaultIndex: string[]; + filterQuery?: Maybe; + histogramType: HistogramType; + inspect: boolean; + sourceId: string; + stackByField: string; + timerange: TimerangeInput; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + MatrixHistogram: MatrixHistogram; + }; + + export type MatrixHistogram = { + __typename?: 'MatrixHistogramOverTimeData'; + + matrixHistogramData: MatrixHistogramData[]; + + totalCount: number; + + inspect: Maybe; + }; + + export type MatrixHistogramData = { + __typename?: 'MatrixOverTimeHistogramData'; + + x: Maybe; + + y: Maybe; + + g: Maybe; + }; + + export type Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; + }; +} + +export namespace SourceQuery { + export type Variables = { + sourceId?: Maybe; + defaultIndex: string[]; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + status: Status; + }; + + export type Status = { + __typename?: 'SourceStatus'; + + indicesExist: boolean; + + indexFields: IndexFields[]; + }; + + export type IndexFields = { + __typename?: 'IndexField'; + + category: string; + + description: Maybe; + + example: Maybe; + + indexes: (Maybe)[]; + + name: string; + + searchable: boolean; + + type: string; + + aggregatable: boolean; + + format: Maybe; + }; +} + export namespace GetAuthenticationsQuery { export type Variables = { sourceId: string; @@ -2680,35 +2814,6 @@ export namespace GetAuthenticationsQuery { }; } -export namespace GetLastEventTimeQuery { - export type Variables = { - sourceId: string; - indexKey: LastEventIndexKey; - details: LastTimeDetails; - defaultIndex: string[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - LastEventTime: LastEventTime; - }; - - export type LastEventTime = { - __typename?: 'LastEventTimeData'; - - lastSeen: Maybe; - }; -} - export namespace GetHostFirstLastSeenQuery { export type Variables = { sourceId: string; @@ -2935,11 +3040,11 @@ export namespace GetHostOverviewQuery { }; } -export namespace GetIpOverviewQuery { +export namespace GetKpiHostDetailsQuery { export type Variables = { sourceId: string; + timerange: TimerangeInput; filterQuery?: Maybe; - ip: string; defaultIndex: string[]; inspect: boolean; }; @@ -2955,154 +3060,106 @@ export namespace GetIpOverviewQuery { id: string; - IpOverview: Maybe; + KpiHostDetails: KpiHostDetails; }; - export type IpOverview = { - __typename?: 'IpOverviewData'; + export type KpiHostDetails = { + __typename?: 'KpiHostDetailsData'; - source: Maybe<_Source>; + authSuccess: Maybe; - destination: Maybe; + authSuccessHistogram: Maybe; - host: Host; + authFailure: Maybe; - inspect: Maybe; - }; + authFailureHistogram: Maybe; - export type _Source = { - __typename?: 'Overview'; + uniqueSourceIps: Maybe; - firstSeen: Maybe; + uniqueSourceIpsHistogram: Maybe; - lastSeen: Maybe; + uniqueDestinationIps: Maybe; - autonomousSystem: AutonomousSystem; + uniqueDestinationIpsHistogram: Maybe; - geo: Geo; + inspect: Maybe; }; - export type AutonomousSystem = { - __typename?: 'AutonomousSystem'; + export type AuthSuccessHistogram = KpiHostDetailsChartFields.Fragment; - number: Maybe; + export type AuthFailureHistogram = KpiHostDetailsChartFields.Fragment; - organization: Maybe; - }; + export type UniqueSourceIpsHistogram = KpiHostDetailsChartFields.Fragment; - export type Organization = { - __typename?: 'AutonomousSystemOrganization'; + export type UniqueDestinationIpsHistogram = KpiHostDetailsChartFields.Fragment; - name: Maybe; - }; + export type Inspect = { + __typename?: 'Inspect'; - export type Geo = { - __typename?: 'GeoEcsFields'; + dsl: string[]; - continent_name: Maybe; + response: string[]; + }; +} - city_name: Maybe; +export namespace GetKpiHostsQuery { + export type Variables = { + sourceId: string; + timerange: TimerangeInput; + filterQuery?: Maybe; + defaultIndex: string[]; + inspect: boolean; + }; - country_iso_code: Maybe; + export type Query = { + __typename?: 'Query'; - country_name: Maybe; + source: Source; + }; - location: Maybe; + export type Source = { + __typename?: 'Source'; - region_iso_code: Maybe; + id: string; - region_name: Maybe; + KpiHosts: KpiHosts; }; - export type Location = { - __typename?: 'Location'; + export type KpiHosts = { + __typename?: 'KpiHostsData'; - lat: Maybe; + hosts: Maybe; - lon: Maybe; - }; + hostsHistogram: Maybe; - export type Destination = { - __typename?: 'Overview'; + authSuccess: Maybe; - firstSeen: Maybe; + authSuccessHistogram: Maybe; - lastSeen: Maybe; + authFailure: Maybe; - autonomousSystem: _AutonomousSystem; + authFailureHistogram: Maybe; - geo: _Geo; - }; - - export type _AutonomousSystem = { - __typename?: 'AutonomousSystem'; - - number: Maybe; - - organization: Maybe<_Organization>; - }; - - export type _Organization = { - __typename?: 'AutonomousSystemOrganization'; - - name: Maybe; - }; - - export type _Geo = { - __typename?: 'GeoEcsFields'; - - continent_name: Maybe; - - city_name: Maybe; - - country_iso_code: Maybe; - - country_name: Maybe; - - location: Maybe<_Location>; - - region_iso_code: Maybe; - - region_name: Maybe; - }; - - export type _Location = { - __typename?: 'Location'; - - lat: Maybe; - - lon: Maybe; - }; - - export type Host = { - __typename?: 'HostEcsFields'; - - architecture: Maybe; - - id: Maybe; - - ip: Maybe; + uniqueSourceIps: Maybe; - mac: Maybe; + uniqueSourceIpsHistogram: Maybe; - name: Maybe; + uniqueDestinationIps: Maybe; - os: Maybe; + uniqueDestinationIpsHistogram: Maybe; - type: Maybe; + inspect: Maybe; }; - export type Os = { - __typename?: 'OsEcsFields'; + export type HostsHistogram = KpiHostChartFields.Fragment; - family: Maybe; + export type AuthSuccessHistogram = KpiHostChartFields.Fragment; - name: Maybe; + export type AuthFailureHistogram = KpiHostChartFields.Fragment; - platform: Maybe; + export type UniqueSourceIpsHistogram = KpiHostChartFields.Fragment; - version: Maybe; - }; + export type UniqueDestinationIpsHistogram = KpiHostChartFields.Fragment; export type Inspect = { __typename?: 'Inspect'; @@ -3113,10 +3170,11 @@ export namespace GetIpOverviewQuery { }; } -export namespace GetKpiHostDetailsQuery { +export namespace GetUncommonProcessesQuery { export type Variables = { sourceId: string; timerange: TimerangeInput; + pagination: PaginationInputPaginated; filterQuery?: Maybe; defaultIndex: string[]; inspect: boolean; @@ -3133,106 +3191,80 @@ export namespace GetKpiHostDetailsQuery { id: string; - KpiHostDetails: KpiHostDetails; + UncommonProcesses: UncommonProcesses; }; - export type KpiHostDetails = { - __typename?: 'KpiHostDetailsData'; - - authSuccess: Maybe; - - authSuccessHistogram: Maybe; - - authFailure: Maybe; - - authFailureHistogram: Maybe; - - uniqueSourceIps: Maybe; + export type UncommonProcesses = { + __typename?: 'UncommonProcessesData'; - uniqueSourceIpsHistogram: Maybe; + totalCount: number; - uniqueDestinationIps: Maybe; + edges: Edges[]; - uniqueDestinationIpsHistogram: Maybe; + pageInfo: PageInfo; inspect: Maybe; }; - export type AuthSuccessHistogram = KpiHostDetailsChartFields.Fragment; - - export type AuthFailureHistogram = KpiHostDetailsChartFields.Fragment; - - export type UniqueSourceIpsHistogram = KpiHostDetailsChartFields.Fragment; - - export type UniqueDestinationIpsHistogram = KpiHostDetailsChartFields.Fragment; - - export type Inspect = { - __typename?: 'Inspect'; + export type Edges = { + __typename?: 'UncommonProcessesEdges'; - dsl: string[]; + node: Node; - response: string[]; + cursor: Cursor; }; -} -export namespace GetKpiHostsQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; + export type Node = { + __typename?: 'UncommonProcessItem'; - export type Query = { - __typename?: 'Query'; + _id: string; - source: Source; - }; + instances: number; - export type Source = { - __typename?: 'Source'; + process: Process; - id: string; + user: Maybe; - KpiHosts: KpiHosts; + hosts: Hosts[]; }; - export type KpiHosts = { - __typename?: 'KpiHostsData'; - - hosts: Maybe; - - hostsHistogram: Maybe; + export type Process = { + __typename?: 'ProcessEcsFields'; - authSuccess: Maybe; + args: Maybe; - authSuccessHistogram: Maybe; + name: Maybe; + }; - authFailure: Maybe; + export type User = { + __typename?: 'UserEcsFields'; - authFailureHistogram: Maybe; + id: Maybe; - uniqueSourceIps: Maybe; + name: Maybe; + }; - uniqueSourceIpsHistogram: Maybe; + export type Hosts = { + __typename?: 'HostEcsFields'; - uniqueDestinationIps: Maybe; + name: Maybe; + }; - uniqueDestinationIpsHistogram: Maybe; + export type Cursor = { + __typename?: 'CursorType'; - inspect: Maybe; + value: Maybe; }; - export type HostsHistogram = KpiHostChartFields.Fragment; - - export type AuthSuccessHistogram = KpiHostChartFields.Fragment; + export type PageInfo = { + __typename?: 'PageInfoPaginated'; - export type AuthFailureHistogram = KpiHostChartFields.Fragment; + activePage: number; - export type UniqueSourceIpsHistogram = KpiHostChartFields.Fragment; + fakeTotalCount: number; - export type UniqueDestinationIpsHistogram = KpiHostChartFields.Fragment; + showMorePagesIndicator: boolean; + }; export type Inspect = { __typename?: 'Inspect'; @@ -3243,11 +3275,11 @@ export namespace GetKpiHostsQuery { }; } -export namespace GetKpiNetworkQuery { +export namespace GetIpOverviewQuery { export type Variables = { sourceId: string; - timerange: TimerangeInput; filterQuery?: Maybe; + ip: string; defaultIndex: string[]; inspect: boolean; }; @@ -3263,89 +3295,213 @@ export namespace GetKpiNetworkQuery { id: string; - KpiNetwork: Maybe; + IpOverview: Maybe; }; - export type KpiNetwork = { - __typename?: 'KpiNetworkData'; - - networkEvents: Maybe; - - uniqueFlowId: Maybe; - - uniqueSourcePrivateIps: Maybe; - - uniqueSourcePrivateIpsHistogram: Maybe; - - uniqueDestinationPrivateIps: Maybe; + export type IpOverview = { + __typename?: 'IpOverviewData'; - uniqueDestinationPrivateIpsHistogram: Maybe; + source: Maybe<_Source>; - dnsQueries: Maybe; + destination: Maybe; - tlsHandshakes: Maybe; + host: Host; inspect: Maybe; }; - export type UniqueSourcePrivateIpsHistogram = KpiNetworkChartFields.Fragment; + export type _Source = { + __typename?: 'Overview'; - export type UniqueDestinationPrivateIpsHistogram = KpiNetworkChartFields.Fragment; + firstSeen: Maybe; - export type Inspect = { - __typename?: 'Inspect'; + lastSeen: Maybe; - dsl: string[]; + autonomousSystem: AutonomousSystem; - response: string[]; + geo: Geo; }; -} -export namespace GetMatrixHistogramQuery { - export type Variables = { - defaultIndex: string[]; - filterQuery?: Maybe; - histogramType: HistogramType; - inspect: boolean; - sourceId: string; - stackByField: string; - timerange: TimerangeInput; - }; + export type AutonomousSystem = { + __typename?: 'AutonomousSystem'; - export type Query = { - __typename?: 'Query'; + number: Maybe; - source: Source; + organization: Maybe; }; - export type Source = { - __typename?: 'Source'; - - id: string; + export type Organization = { + __typename?: 'AutonomousSystemOrganization'; - MatrixHistogram: MatrixHistogram; + name: Maybe; }; - export type MatrixHistogram = { - __typename?: 'MatrixHistogramOverTimeData'; + export type Geo = { + __typename?: 'GeoEcsFields'; - matrixHistogramData: MatrixHistogramData[]; + continent_name: Maybe; - totalCount: number; + city_name: Maybe; - inspect: Maybe; - }; + country_iso_code: Maybe; - export type MatrixHistogramData = { - __typename?: 'MatrixOverTimeHistogramData'; + country_name: Maybe; - x: Maybe; + location: Maybe; - y: Maybe; + region_iso_code: Maybe; - g: Maybe; + region_name: Maybe; + }; + + export type Location = { + __typename?: 'Location'; + + lat: Maybe; + + lon: Maybe; + }; + + export type Destination = { + __typename?: 'Overview'; + + firstSeen: Maybe; + + lastSeen: Maybe; + + autonomousSystem: _AutonomousSystem; + + geo: _Geo; + }; + + export type _AutonomousSystem = { + __typename?: 'AutonomousSystem'; + + number: Maybe; + + organization: Maybe<_Organization>; + }; + + export type _Organization = { + __typename?: 'AutonomousSystemOrganization'; + + name: Maybe; + }; + + export type _Geo = { + __typename?: 'GeoEcsFields'; + + continent_name: Maybe; + + city_name: Maybe; + + country_iso_code: Maybe; + + country_name: Maybe; + + location: Maybe<_Location>; + + region_iso_code: Maybe; + + region_name: Maybe; + }; + + export type _Location = { + __typename?: 'Location'; + + lat: Maybe; + + lon: Maybe; + }; + + export type Host = { + __typename?: 'HostEcsFields'; + + architecture: Maybe; + + id: Maybe; + + ip: Maybe; + + mac: Maybe; + + name: Maybe; + + os: Maybe; + + type: Maybe; + }; + + export type Os = { + __typename?: 'OsEcsFields'; + + family: Maybe; + + name: Maybe; + + platform: Maybe; + + version: Maybe; + }; + + export type Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; + }; +} + +export namespace GetKpiNetworkQuery { + export type Variables = { + sourceId: string; + timerange: TimerangeInput; + filterQuery?: Maybe; + defaultIndex: string[]; + inspect: boolean; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + KpiNetwork: Maybe; + }; + + export type KpiNetwork = { + __typename?: 'KpiNetworkData'; + + networkEvents: Maybe; + + uniqueFlowId: Maybe; + + uniqueSourcePrivateIps: Maybe; + + uniqueSourcePrivateIpsHistogram: Maybe; + + uniqueDestinationPrivateIps: Maybe; + + uniqueDestinationPrivateIpsHistogram: Maybe; + + dnsQueries: Maybe; + + tlsHandshakes: Maybe; + + inspect: Maybe; }; + export type UniqueSourcePrivateIpsHistogram = KpiNetworkChartFields.Fragment; + + export type UniqueDestinationPrivateIpsHistogram = KpiNetworkChartFields.Fragment; + export type Inspect = { __typename?: 'Inspect'; @@ -3832,11 +3988,15 @@ export namespace GetNetworkTopNFlowQuery { }; } -export namespace GetOverviewHostQuery { +export namespace GetTlsQuery { export type Variables = { sourceId: string; - timerange: TimerangeInput; filterQuery?: Maybe; + flowTarget: FlowTargetSourceDest; + ip: string; + pagination: PaginationInputPaginated; + sort: TlsSortField; + timerange: TimerangeInput; defaultIndex: string[]; inspect: boolean; }; @@ -3852,45 +4012,57 @@ export namespace GetOverviewHostQuery { id: string; - OverviewHost: Maybe; + Tls: Tls; }; - export type OverviewHost = { - __typename?: 'OverviewHostData'; + export type Tls = { + __typename?: 'TlsData'; - auditbeatAuditd: Maybe; + totalCount: number; - auditbeatFIM: Maybe; + edges: Edges[]; - auditbeatLogin: Maybe; + pageInfo: PageInfo; - auditbeatPackage: Maybe; + inspect: Maybe; + }; - auditbeatProcess: Maybe; + export type Edges = { + __typename?: 'TlsEdges'; - auditbeatUser: Maybe; + node: Node; - endgameDns: Maybe; + cursor: Cursor; + }; - endgameFile: Maybe; + export type Node = { + __typename?: 'TlsNode'; - endgameImageLoad: Maybe; + _id: Maybe; - endgameNetwork: Maybe; + subjects: Maybe; - endgameProcess: Maybe; + ja3: Maybe; - endgameRegistry: Maybe; + issuers: Maybe; - endgameSecurity: Maybe; + notAfter: Maybe; + }; - filebeatSystemModule: Maybe; + export type Cursor = { + __typename?: 'CursorType'; - winlogbeatSecurity: Maybe; + value: Maybe; + }; - winlogbeatMWSysmonOperational: Maybe; + export type PageInfo = { + __typename?: 'PageInfoPaginated'; - inspect: Maybe; + activePage: number; + + fakeTotalCount: number; + + showMorePagesIndicator: boolean; }; export type Inspect = { @@ -3902,11 +4074,15 @@ export namespace GetOverviewHostQuery { }; } -export namespace GetOverviewNetworkQuery { +export namespace GetUsersQuery { export type Variables = { sourceId: string; - timerange: TimerangeInput; filterQuery?: Maybe; + flowTarget: FlowTarget; + ip: string; + pagination: PaginationInputPaginated; + sort: UsersSortField; + timerange: TimerangeInput; defaultIndex: string[]; inspect: boolean; }; @@ -3922,35 +4098,67 @@ export namespace GetOverviewNetworkQuery { id: string; - OverviewNetwork: Maybe; + Users: Users; }; - export type OverviewNetwork = { - __typename?: 'OverviewNetworkData'; - - auditbeatSocket: Maybe; + export type Users = { + __typename?: 'UsersData'; - filebeatCisco: Maybe; + totalCount: number; - filebeatNetflow: Maybe; + edges: Edges[]; - filebeatPanw: Maybe; + pageInfo: PageInfo; - filebeatSuricata: Maybe; + inspect: Maybe; + }; - filebeatZeek: Maybe; + export type Edges = { + __typename?: 'UsersEdges'; - packetbeatDNS: Maybe; + node: Node; - packetbeatFlow: Maybe; + cursor: Cursor; + }; - packetbeatTLS: Maybe; + export type Node = { + __typename?: 'UsersNode'; - inspect: Maybe; + user: Maybe; }; - export type Inspect = { - __typename?: 'Inspect'; + export type User = { + __typename?: 'UsersItem'; + + name: Maybe; + + id: Maybe; + + groupId: Maybe; + + groupName: Maybe; + + count: Maybe; + }; + + export type Cursor = { + __typename?: 'CursorType'; + + value: Maybe; + }; + + export type PageInfo = { + __typename?: 'PageInfoPaginated'; + + activePage: number; + + fakeTotalCount: number; + + showMorePagesIndicator: boolean; + }; + + export type Inspect = { + __typename?: 'Inspect'; dsl: string[]; @@ -3958,10 +4166,13 @@ export namespace GetOverviewNetworkQuery { }; } -export namespace SourceQuery { +export namespace GetOverviewHostQuery { export type Variables = { - sourceId?: Maybe; + sourceId: string; + timerange: TimerangeInput; + filterQuery?: Maybe; defaultIndex: string[]; + inspect: boolean; }; export type Query = { @@ -3975,37 +4186,109 @@ export namespace SourceQuery { id: string; - status: Status; + OverviewHost: Maybe; }; - export type Status = { - __typename?: 'SourceStatus'; + export type OverviewHost = { + __typename?: 'OverviewHostData'; - indicesExist: boolean; + auditbeatAuditd: Maybe; - indexFields: IndexFields[]; + auditbeatFIM: Maybe; + + auditbeatLogin: Maybe; + + auditbeatPackage: Maybe; + + auditbeatProcess: Maybe; + + auditbeatUser: Maybe; + + endgameDns: Maybe; + + endgameFile: Maybe; + + endgameImageLoad: Maybe; + + endgameNetwork: Maybe; + + endgameProcess: Maybe; + + endgameRegistry: Maybe; + + endgameSecurity: Maybe; + + filebeatSystemModule: Maybe; + + winlogbeatSecurity: Maybe; + + winlogbeatMWSysmonOperational: Maybe; + + inspect: Maybe; }; - export type IndexFields = { - __typename?: 'IndexField'; + export type Inspect = { + __typename?: 'Inspect'; - category: string; + dsl: string[]; - description: Maybe; + response: string[]; + }; +} - example: Maybe; +export namespace GetOverviewNetworkQuery { + export type Variables = { + sourceId: string; + timerange: TimerangeInput; + filterQuery?: Maybe; + defaultIndex: string[]; + inspect: boolean; + }; - indexes: (Maybe)[]; + export type Query = { + __typename?: 'Query'; - name: string; + source: Source; + }; - searchable: boolean; + export type Source = { + __typename?: 'Source'; - type: string; + id: string; - aggregatable: boolean; + OverviewNetwork: Maybe; + }; - format: Maybe; + export type OverviewNetwork = { + __typename?: 'OverviewNetworkData'; + + auditbeatSocket: Maybe; + + filebeatCisco: Maybe; + + filebeatNetflow: Maybe; + + filebeatPanw: Maybe; + + filebeatSuricata: Maybe; + + filebeatZeek: Maybe; + + packetbeatDNS: Maybe; + + packetbeatFlow: Maybe; + + packetbeatTLS: Maybe; + + inspect: Maybe; + }; + + export type Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; }; } @@ -5662,289 +5945,6 @@ export namespace PersistTimelinePinnedEventMutation { }; } -export namespace GetTlsQuery { - export type Variables = { - sourceId: string; - filterQuery?: Maybe; - flowTarget: FlowTargetSourceDest; - ip: string; - pagination: PaginationInputPaginated; - sort: TlsSortField; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - Tls: Tls; - }; - - export type Tls = { - __typename?: 'TlsData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'TlsEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'TlsNode'; - - _id: Maybe; - - subjects: Maybe; - - ja3: Maybe; - - issuers: Maybe; - - notAfter: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetUncommonProcessesQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - pagination: PaginationInputPaginated; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - UncommonProcesses: UncommonProcesses; - }; - - export type UncommonProcesses = { - __typename?: 'UncommonProcessesData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'UncommonProcessesEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'UncommonProcessItem'; - - _id: string; - - instances: number; - - process: Process; - - user: Maybe; - - hosts: Hosts[]; - }; - - export type Process = { - __typename?: 'ProcessEcsFields'; - - args: Maybe; - - name: Maybe; - }; - - export type User = { - __typename?: 'UserEcsFields'; - - id: Maybe; - - name: Maybe; - }; - - export type Hosts = { - __typename?: 'HostEcsFields'; - - name: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetUsersQuery { - export type Variables = { - sourceId: string; - filterQuery?: Maybe; - flowTarget: FlowTarget; - ip: string; - pagination: PaginationInputPaginated; - sort: UsersSortField; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - Users: Users; - }; - - export type Users = { - __typename?: 'UsersData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'UsersEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'UsersNode'; - - user: Maybe; - }; - - export type User = { - __typename?: 'UsersItem'; - - name: Maybe; - - id: Maybe; - - groupId: Maybe; - - groupName: Maybe; - - count: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - export namespace KpiHostDetailsChartFields { export type Fragment = { __typename?: 'KpiHostHistogramData'; diff --git a/x-pack/plugins/siem/scripts/generate_types_from_graphql.js b/x-pack/plugins/siem/scripts/generate_types_from_graphql.js index e6b063dfd2c07..21a37e31c6e80 100644 --- a/x-pack/plugins/siem/scripts/generate_types_from_graphql.js +++ b/x-pack/plugins/siem/scripts/generate_types_from_graphql.js @@ -11,7 +11,7 @@ const { join, resolve } = require('path'); const { generate } = require('graphql-code-generator'); const GRAPHQL_GLOBS = [ - join('public', 'containers', '**', '*.gql_query.ts{,x}'), + join('public', '**', '*.gql_query.ts{,x}'), join('common', 'graphql', '**', '*.gql_query.ts{,x}'), ]; const OUTPUT_INTROSPECTION_PATH = resolve('public', 'graphql', 'introspection.json'); From 09c950ba6b00accdce44229c0d10c0a1728019fc Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 15 May 2020 16:28:50 -0600 Subject: [PATCH 06/18] [Maps] Do not check count for blended layers when layer is not visible (#66460) * [Maps] Do not check count for blended layers when layer is not visible * move visibility logic to map_actions where syncData is called * clean up * fix syntax error * remove async action * make syncDataForAllLayers a proper redux action * simplify * fix functional test Co-authored-by: Elastic Machine --- .../maps/public/actions/map_actions.js | 81 ++++++++++--------- .../classes/layers/tile_layer/tile_layer.js | 3 - .../tiled_vector_layer/tiled_vector_layer.tsx | 4 - .../layers/vector_layer/vector_layer.js | 4 - .../vector_tile_layer/vector_tile_layer.js | 4 - .../es_archives/maps/kibana/data.json | 8 +- 6 files changed, 47 insertions(+), 57 deletions(-) diff --git a/x-pack/plugins/maps/public/actions/map_actions.js b/x-pack/plugins/maps/public/actions/map_actions.js index 5aa7c74a7b30b..1dfdfc3a73d8a 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.js +++ b/x-pack/plugins/maps/public/actions/map_actions.js @@ -111,14 +111,36 @@ function getLayerById(layerId, state) { }); } -async function syncDataForAllLayers(dispatch, getState, dataFilters) { - const state = getState(); - const layerList = getLayerList(state); - const syncs = layerList.map(layer => { - const loadingFunctions = getLayerLoadingCallbacks(dispatch, getState, layer.getId()); - return layer.syncData({ ...loadingFunctions, dataFilters }); - }); - await Promise.all(syncs); +function syncDataForAllLayers() { + return async (dispatch, getState) => { + const syncPromises = getLayerList(getState()).map(async layer => { + return dispatch(syncDataForLayer(layer)); + }); + await Promise.all(syncPromises); + }; +} + +function syncDataForLayer(layer) { + return async (dispatch, getState) => { + const dataFilters = getDataFilters(getState()); + if (!layer.isVisible() || !layer.showAtZoomLevel(dataFilters.zoom)) { + return; + } + + await layer.syncData({ + ...getLayerLoadingCallbacks(dispatch, getState, layer.getId()), + dataFilters, + }); + }; +} + +function syncDataForLayerId(layerId) { + return async (dispatch, getState) => { + const layer = getLayerById(layerId, getState()); + if (layer) { + dispatch(syncDataForLayer(layer)); + } + }; } export function cancelAllInFlightRequests() { @@ -192,7 +214,7 @@ export function rollbackToTrackedLayerStateForSelectedLayer() { // syncDataForLayer may not trigger endDataLoad if no re-fetch is required dispatch(updateStyleMeta(layerId)); - dispatch(syncDataForLayer(layerId)); + dispatch(syncDataForLayerId(layerId)); }; } @@ -252,7 +274,7 @@ export function addLayer(layerDescriptor) { type: ADD_LAYER, layer: layerDescriptor, }); - dispatch(syncDataForLayer(layerDescriptor.id)); + dispatch(syncDataForLayerId(layerDescriptor.id)); }; } @@ -333,7 +355,7 @@ export function setLayerVisibility(layerId, makeVisible) { visibility: makeVisible, }); if (makeVisible) { - dispatch(syncDataForLayer(layerId)); + dispatch(syncDataForLayer(layer)); } }; } @@ -466,8 +488,7 @@ export function mapExtentChanged(newMapConstants) { ...newMapConstants, }, }); - const newDataFilters = { ...dataFilters, ...newMapConstants }; - await syncDataForAllLayers(dispatch, getState, newDataFilters); + await dispatch(syncDataForAllLayers()); }; } @@ -751,7 +772,7 @@ export function updateSourceProp(layerId, propName, value, newLayerType) { dispatch(updateLayerType(layerId, newLayerType)); } await dispatch(clearMissingStyleProperties(layerId)); - dispatch(syncDataForLayer(layerId)); + dispatch(syncDataForLayerId(layerId)); }; } @@ -771,20 +792,6 @@ function updateLayerType(layerId, newLayerType) { }; } -export function syncDataForLayer(layerId) { - return async (dispatch, getState) => { - const targetLayer = getLayerById(layerId, getState()); - if (targetLayer) { - const dataFilters = getDataFilters(getState()); - const loadingFunctions = getLayerLoadingCallbacks(dispatch, getState, layerId); - await targetLayer.syncData({ - ...loadingFunctions, - dataFilters, - }); - } - }; -} - export function updateLayerLabel(id, newLabel) { return { type: UPDATE_LAYER_PROP, @@ -830,7 +837,7 @@ export function setLayerQuery(id, query) { newValue: query, }); - dispatch(syncDataForLayer(id)); + dispatch(syncDataForLayerId(id)); }; } @@ -895,8 +902,7 @@ export function setQuery({ query, timeFilters, filters = [], refresh = false }) filters, }); - const dataFilters = getDataFilters(getState()); - await syncDataForAllLayers(dispatch, getState, dataFilters); + await dispatch(syncDataForAllLayers()); }; } @@ -909,13 +915,12 @@ export function setRefreshConfig({ isPaused, interval }) { } export function triggerRefreshTimer() { - return async (dispatch, getState) => { + return async dispatch => { dispatch({ type: TRIGGER_REFRESH_TIMER, }); - const dataFilters = getDataFilters(getState()); - await syncDataForAllLayers(dispatch, getState, dataFilters); + await dispatch(syncDataForAllLayers()); }; } @@ -956,7 +961,7 @@ export function updateLayerStyle(layerId, styleDescriptor) { dispatch(updateStyleMeta(layerId)); // Style update may require re-fetch, for example ES search may need to retrieve field used for dynamic styling - dispatch(syncDataForLayer(layerId)); + dispatch(syncDataForLayerId(layerId)); }; } @@ -994,12 +999,12 @@ export function setJoinsForLayer(layer, joins) { return async dispatch => { await dispatch({ type: SET_JOINS, - layer: layer, - joins: joins, + layer, + joins, }); await dispatch(clearMissingStyleProperties(layer.getId())); - dispatch(syncDataForLayer(layer.getId())); + dispatch(syncDataForLayerId(layer.getId())); }; } diff --git a/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js b/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js index 69f5033e3af0f..5108e5cd3e6a3 100644 --- a/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js @@ -25,9 +25,6 @@ export class TileLayer extends AbstractLayer { } async syncData({ startLoading, stopLoading, onLoadError, dataFilters }) { - if (!this.isVisible() || !this.showAtZoomLevel(dataFilters.zoom)) { - return; - } const sourceDataRequest = this.getSourceDataRequest(); if (sourceDataRequest) { //data is immmutable diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index bb4fbe9d01b60..f8f78f6e97829 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -78,10 +78,6 @@ export class TiledVectorLayer extends VectorLayer { } async syncData(syncContext: SyncContext) { - if (!this.isVisible() || !this.showAtZoomLevel(syncContext.dataFilters.zoom)) { - return; - } - await this._syncSourceStyleMeta(syncContext, this._source, this._style); await this._syncSourceFormatters(syncContext, this._source, this._style); await this._syncMVTUrlTemplate(syncContext); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js index ccbc8a1c21324..d32593f73c46c 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js @@ -571,10 +571,6 @@ export class VectorLayer extends AbstractLayer { // Given 2 above, which source/style to use can not be pulled from data request state. // Therefore, source and style are provided as arugments and must be used instead of calling getSource or getCurrentStyle. async _syncData(syncContext, source, style) { - if (!this.isVisible() || !this.showAtZoomLevel(syncContext.dataFilters.zoom)) { - return; - } - await this._syncSourceStyleMeta(syncContext, source, style); await this._syncSourceFormatters(syncContext, source, style); const sourceResult = await this._syncSource(syncContext, source, style); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js index fe1ff58922162..4ffd0d93fd22a 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js @@ -45,10 +45,6 @@ export class VectorTileLayer extends TileLayer { } async syncData({ startLoading, stopLoading, onLoadError, dataFilters }) { - if (!this.isVisible() || !this.showAtZoomLevel(dataFilters.zoom)) { - return; - } - const nextMeta = { tileLayerId: this.getSource().getTileLayerId() }; const canSkipSync = this._canSkipSync({ prevDataRequest: this.getSourceDataRequest(), diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index cb3598652a39a..d313fd2046c03 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -233,7 +233,7 @@ "title" : "document example top hits", "description" : "", "mapStateJSON" : "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-24T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"}}", - "layerListJSON" : "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"topHitsTimeField\":\"@timestamp\",\"useTopHits\":true,\"topHitsSplitField\":\"machine.os.raw\",\"topHitsSize\":2,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]", + "layerListJSON" : "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"topHitsTimeField\":\"@timestamp\",\"useTopHits\":true,\"topHitsSplitField\":\"machine.os.raw\",\"topHitsSize\":2,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]", "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}", "bounds" : { "type" : "polygon", @@ -467,7 +467,7 @@ "type": "envelope" }, "description": "", - "layerListJSON" : "[{\"id\":\"0hmz5\",\"label\":\"EMS base layer (road_map)\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"applyGlobalQuery\":false,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3},\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_1_join_0_index_pattern\"}}]}]", + "layerListJSON" : "[{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"applyGlobalQuery\":false,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3},\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_1_join_0_index_pattern\"}}]}]", "mapStateJSON": "{\"zoom\":3.02,\"center\":{\"lon\":77.33426,\"lat\":-0.04647},\"timeFilters\":{\"from\":\"now-17m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}", "title": "join example", "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"n1t6f\"]}" @@ -512,7 +512,7 @@ ], "type": "envelope" }, - "layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"3xlvm\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"427aa49d-a552-4e7d-a629-67c47db27128\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"heatmap\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"HEATMAP\",\"refinement\":\"coarse\",\"properties\":{},\"previousStyle\":null},\"type\":\"HEATMAP\"}]", + "layerListJSON": "[{\"id\":\"3xlvm\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"427aa49d-a552-4e7d-a629-67c47db27128\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"heatmap\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"HEATMAP\",\"refinement\":\"coarse\",\"properties\":{},\"previousStyle\":null},\"type\":\"HEATMAP\"}]", "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}", "title": "geo grid heatmap example", "uiStateJSON": "{\"isDarkMode\":false}" @@ -543,7 +543,7 @@ "type": "envelope" }, "description": "", - "layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]", + "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]", "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}", "title": "geo grid vector grid example", "uiStateJSON": "{\"isDarkMode\":false}" From ac0946069b95faeeb881cdfc2e1e1456f7e7df43 Mon Sep 17 00:00:00 2001 From: Andrew Cholakian Date: Fri, 15 May 2020 17:34:55 -0500 Subject: [PATCH 07/18] [Uptime] Fix flaky navigation to certs page in tests (#66806) Fix flaky navigation to certs page in tests. Fixes https://github.com/elastic/kibana/issues/66778 --- x-pack/test/functional/services/uptime/navigation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index 37cc71d6865b0..d372bd53c081b 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -65,8 +65,8 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv }, goToCertificates: async () => { - await testSubjects.click('uptimeCertificatesLink', 10000); - return retry.tryForTime(60 * 1000, async () => { + return retry.try(async () => { + await testSubjects.click('uptimeCertificatesLink'); await testSubjects.existOrFail('uptimeCertificatesPage'); }); }, From bfdeb10c15027a3784c733e80641348a0d20e429 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 15 May 2020 16:11:27 -0700 Subject: [PATCH 08/18] Reorganize Management apps into Ingest, Data, Alerts and Insights, Security, Kibana, and Stack groups (#65796) --- .../sections/index_patterns/index.js | 3 +- .../advanced_settings/public/plugin.ts | 9 +-- .../public/search/long_query_notification.tsx | 4 +- .../management_sidebar_nav/_sidebar_nav.scss | 2 +- .../management_sidebar_nav.tsx | 4 +- src/plugins/management/public/index.ts | 1 + .../public/legacy/sections_register.js | 31 ++------ .../public/management_section.test.ts | 3 +- .../management/public/management_section.ts | 8 +- .../management/public/management_sections.tsx | 77 +++++++++++++++++++ .../public/management_service.test.ts | 31 ++------ .../management/public/management_service.ts | 31 +++++--- src/plugins/management/public/mocks/index.ts | 1 - src/plugins/management/public/types.ts | 38 +++++++-- .../saved_objects_management/public/plugin.ts | 9 +-- .../management_test_plugin/public/plugin.tsx | 15 ++-- .../management/management_plugin.js | 2 +- .../common/constants/index.ts | 2 +- .../AlertIntegrations/index.tsx | 2 +- .../ServiceIntegrations/WatcherFlyout.tsx | 2 +- .../ServiceIntegrations/index.tsx | 2 +- .../app/ServiceOverview/NoServicesMessage.tsx | 2 +- .../NoServicesMessage.test.tsx.snap | 4 +- .../ServiceOverview.test.tsx.snap | 2 +- .../components/app/ServiceOverview/index.tsx | 2 +- .../components/shared/LicensePrompt/index.tsx | 2 +- .../InvalidLicenseNotification.tsx | 2 +- x-pack/plugins/apm/public/setHelpExtension.ts | 2 +- .../beats_management/public/application.tsx | 7 +- .../beats_management/public/bootstrap.tsx | 30 ++------ .../lib/adapters/framework/adapter_types.ts | 14 +--- .../framework/kibana_framework_adapter.ts | 43 +++-------- .../framework/testing_framework_adapter.ts | 9 --- .../beats_management/public/lib/framework.ts | 1 - .../common/constants/index.ts | 4 +- .../public/plugin.ts | 5 +- .../extend_index_management.test.js.snap | 8 +- .../__snapshots__/policy_table.test.js.snap | 2 +- .../common/constants/index.ts | 2 +- .../public/plugin.tsx | 5 +- .../common/constants/base_path.ts | 2 +- .../public/application/services/navigation.ts | 4 +- .../plugins/index_management/public/plugin.ts | 6 +- .../components/alert_dropdown.tsx | 2 +- .../alerting/inventory/alert_dropdown.tsx | 2 +- .../alerting/logs/alert_dropdown.tsx | 2 +- .../ingest_pipelines/common/constants.ts | 2 +- .../plugins/ingest_pipelines/public/plugin.ts | 3 +- .../__snapshots__/add_license.test.js.snap | 4 +- .../upload_license.test.tsx.snap | 20 ++--- .../common/constants/base_path.ts | 2 +- .../license_management/public/plugin.ts | 6 +- .../plugins/licensing/public/plugin.test.ts | 2 +- x-pack/plugins/licensing/public/plugin.ts | 2 +- .../public/application/breadcrumbs.js | 2 +- x-pack/plugins/logstash/public/plugin.ts | 39 +++++----- .../datavisualizer_selector.tsx | 2 +- .../results_links/results_links.tsx | 2 +- .../create_watch_service.js | 2 +- .../ml/public/application/management/index.ts | 23 +++--- .../overview/components/sidebar.tsx | 2 +- .../components/cluster/listing/listing.js | 2 +- .../public/components/license/index.js | 2 +- .../public/application/constants/paths.ts | 2 +- .../plugins/remote_clusters/public/plugin.ts | 7 +- x-pack/plugins/reporting/public/plugin.tsx | 6 +- .../rollup/public/crud_app/constants/paths.js | 2 +- x-pack/plugins/rollup/public/plugin.ts | 37 ++++----- .../management/management_service.test.ts | 14 +--- .../public/management/management_service.ts | 13 +--- .../rules/select_rule_type/index.tsx | 2 +- .../upgrade_contents.test.tsx.snap | 2 +- .../ml_popover/upgrade_contents.tsx | 2 +- .../public/application/constants/index.ts | 2 +- .../plugins/snapshot_restore/public/plugin.ts | 6 +- .../management/management_service.test.ts | 2 +- .../public/management/management_service.tsx | 13 ++-- .../management/spaces_management_app.test.tsx | 2 +- .../management/spaces_management_app.tsx | 2 +- x-pack/plugins/spaces/public/plugin.test.ts | 4 +- .../transform/public/app/constants/index.ts | 2 +- x-pack/plugins/transform/public/plugin.ts | 28 ++++--- .../transform/public/register_feature.ts | 2 +- .../translations/translations/ja-JP.json | 5 -- .../translations/translations/zh-CN.json | 5 -- .../public/application/constants/index.ts | 2 +- .../connector_add_flyout.test.tsx | 2 +- .../triggers_actions_ui/public/plugin.ts | 6 +- .../upgrade_assistant/public/plugin.ts | 6 +- .../__snapshots__/license_info.test.tsx.snap | 4 +- .../__snapshots__/ml_flyout.test.tsx.snap | 2 +- .../components/monitor/ml/license_info.tsx | 2 +- .../alerts/toggle_alert_flyout_button.tsx | 2 +- .../client_integration/watch_list.test.ts | 2 +- .../watcher/public/application/app.tsx | 2 +- .../public/application/constants/base_path.ts | 2 +- .../public/application/lib/breadcrumbs.ts | 2 +- .../monitoring_watch_edit.tsx | 4 +- .../watch_list/components/watch_list.tsx | 4 +- x-pack/plugins/watcher/public/plugin.ts | 8 +- .../report_delete_pagination.ts | 2 +- x-pack/test/functional/config.js | 24 +++--- x-pack/test/functional_with_es_ssl/config.ts | 2 +- 103 files changed, 391 insertions(+), 405 deletions(-) create mode 100644 src/plugins/management/public/management_sections.tsx diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js index a8376c0e84bf9..24bc4ba8fba5b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js @@ -18,6 +18,7 @@ */ import { management } from 'ui/management'; +import { ManagementSectionId } from '../../../../../../../plugins/management/public'; import './create_index_pattern_wizard'; import './edit_index_pattern'; import uiRoutes from 'ui/routes'; @@ -163,7 +164,7 @@ uiModules }; }); -management.getSection('kibana').register('index_patterns', { +management.getSection(ManagementSectionId.Kibana).register('index_patterns', { display: i18n.translate('kbn.management.indexPattern.sectionsHeader', { defaultMessage: 'Index Patterns', }), diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts index 04eeff1e1f3ce..2784b74ab726c 100644 --- a/src/plugins/advanced_settings/public/plugin.ts +++ b/src/plugins/advanced_settings/public/plugin.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { ManagementApp } from '../../management/public'; +import { ManagementApp, ManagementSectionId } from '../../management/public'; import { ComponentRegistry } from './component_registry'; import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types'; @@ -32,15 +32,12 @@ export class AdvancedSettingsPlugin implements Plugin { private managementApp?: ManagementApp; public setup(core: CoreSetup, { management }: AdvancedSettingsPluginSetup) { - const kibanaSection = management.sections.getSection('kibana'); - if (!kibanaSection) { - throw new Error('`kibana` management section not found.'); - } + const kibanaSection = management.sections.getSection(ManagementSectionId.Kibana); this.managementApp = kibanaSection.registerApp({ id: 'settings', title, - order: 20, + order: 3, async mount(params) { const { mountManagementSection } = await import( './management_app/mount_management_section' diff --git a/src/plugins/data/public/search/long_query_notification.tsx b/src/plugins/data/public/search/long_query_notification.tsx index 590fee20db690..0bdf8ab7c66f8 100644 --- a/src/plugins/data/public/search/long_query_notification.tsx +++ b/src/plugins/data/public/search/long_query_notification.tsx @@ -44,9 +44,7 @@ export function LongQueryNotification(props: Props) { { - await props.application.navigateToApp( - 'kibana#/management/elasticsearch/license_management' - ); + await props.application.navigateToApp('kibana#/management/stack/license_management'); }} > { + this.main.register(id, { + display: title, + order: idx, + }); }); return this.main; diff --git a/src/plugins/management/public/management_section.test.ts b/src/plugins/management/public/management_section.test.ts index c68175ee0a678..e1d047425ac18 100644 --- a/src/plugins/management/public/management_section.test.ts +++ b/src/plugins/management/public/management_section.test.ts @@ -18,6 +18,7 @@ */ import { ManagementSection } from './management_section'; +import { ManagementSectionId } from './types'; // @ts-ignore import { LegacyManagementSection } from './legacy'; import { coreMock } from '../../../core/public/mocks'; @@ -27,7 +28,7 @@ function createSection(registerLegacyApp: () => void) { const getLegacySection = () => legacySection; const getManagementSections: () => ManagementSection[] = () => []; - const testSectionConfig = { id: 'test-section', title: 'Test Section' }; + const testSectionConfig = { id: ManagementSectionId.Data, title: 'Test Section' }; return new ManagementSection( testSectionConfig, getManagementSections, diff --git a/src/plugins/management/public/management_section.ts b/src/plugins/management/public/management_section.ts index 483605341ae4c..ace8f87bec766 100644 --- a/src/plugins/management/public/management_section.ts +++ b/src/plugins/management/public/management_section.ts @@ -17,7 +17,9 @@ * under the License. */ -import { CreateSection, RegisterManagementAppArgs } from './types'; +import { ReactElement } from 'react'; + +import { CreateSection, RegisterManagementAppArgs, ManagementSectionId } from './types'; import { KibanaLegacySetup } from '../../kibana_legacy/public'; import { StartServicesAccessor } from '../../../core/public'; // @ts-ignore @@ -25,8 +27,8 @@ import { LegacyManagementSection } from './legacy'; import { ManagementApp } from './management_app'; export class ManagementSection { - public readonly id: string = ''; - public readonly title: string = ''; + public readonly id: ManagementSectionId; + public readonly title: string | ReactElement = ''; public readonly apps: ManagementApp[] = []; public readonly order: number; public readonly euiIconType?: string; diff --git a/src/plugins/management/public/management_sections.tsx b/src/plugins/management/public/management_sections.tsx new file mode 100644 index 0000000000000..77e494626a00e --- /dev/null +++ b/src/plugins/management/public/management_sections.tsx @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiIcon } from '@elastic/eui'; + +import { ManagementSectionId } from './types'; + +interface Props { + text: string; + tip: string; +} + +const ManagementSectionTitle = ({ text, tip }: Props) => ( + + + {text} + + + + + + +); + +export const managementSections = [ + { + id: ManagementSectionId.Ingest, + title: ( + + ), + }, + { + id: ManagementSectionId.Data, + title: , + }, + { + id: ManagementSectionId.InsightsAndAlerting, + title: ( + + ), + }, + { + id: ManagementSectionId.Security, + title: , + }, + { + id: ManagementSectionId.Kibana, + title: , + }, + { + id: ManagementSectionId.Stack, + title: , + }, +]; diff --git a/src/plugins/management/public/management_service.test.ts b/src/plugins/management/public/management_service.test.ts index 18569ef285ff3..1507d6f43619d 100644 --- a/src/plugins/management/public/management_service.test.ts +++ b/src/plugins/management/public/management_service.test.ts @@ -18,6 +18,7 @@ */ import { ManagementService } from './management_service'; +import { ManagementSectionId } from './types'; import { coreMock } from '../../../core/public/mocks'; import { npSetup } from '../../../legacy/ui/public/new_platform/__mocks__'; @@ -29,27 +30,11 @@ test('Provides default sections', () => { () => {}, coreMock.createSetup().getStartServices ); - expect(service.getAllSections().length).toEqual(2); - expect(service.getSection('kibana')).not.toBeUndefined(); - expect(service.getSection('elasticsearch')).not.toBeUndefined(); -}); - -test('Register section, enable and disable', () => { - const service = new ManagementService().setup( - npSetup.plugins.kibanaLegacy, - () => {}, - coreMock.createSetup().getStartServices - ); - const testSection = service.register({ id: 'test-section', title: 'Test Section' }); - expect(service.getSection('test-section')).not.toBeUndefined(); - - const testApp = testSection.registerApp({ - id: 'test-app', - title: 'Test App', - mount: () => () => {}, - }); - expect(testSection.getApp('test-app')).not.toBeUndefined(); - expect(service.getSectionsEnabled().length).toEqual(1); - testApp.disable(); - expect(service.getSectionsEnabled().length).toEqual(0); + expect(service.getAllSections().length).toEqual(6); + expect(service.getSection(ManagementSectionId.Ingest)).toBeDefined(); + expect(service.getSection(ManagementSectionId.Data)).toBeDefined(); + expect(service.getSection(ManagementSectionId.InsightsAndAlerting)).toBeDefined(); + expect(service.getSection(ManagementSectionId.Security)).toBeDefined(); + expect(service.getSection(ManagementSectionId.Kibana)).toBeDefined(); + expect(service.getSection(ManagementSectionId.Stack)).toBeDefined(); }); diff --git a/src/plugins/management/public/management_service.ts b/src/plugins/management/public/management_service.ts index 8fc207e32e6ce..85d27a526d402 100644 --- a/src/plugins/management/public/management_service.ts +++ b/src/plugins/management/public/management_service.ts @@ -17,11 +17,14 @@ * under the License. */ +import { ReactElement } from 'react'; + import { ManagementSection } from './management_section'; +import { managementSections } from './management_sections'; import { KibanaLegacySetup } from '../../kibana_legacy/public'; // @ts-ignore -import { LegacyManagementSection } from './legacy'; -import { CreateSection } from './types'; +import { LegacyManagementSection, sections } from './legacy'; +import { CreateSection, ManagementSectionId } from './types'; import { StartServicesAccessor, CoreStart } from '../../../core/public'; export class ManagementService { @@ -48,7 +51,8 @@ export class ManagementService { return newSection; }; } - private getSection(sectionId: ManagementSection['id']) { + + private getSection(sectionId: ManagementSectionId) { return this.sections.find(section => section.id === sectionId); } @@ -63,7 +67,13 @@ export class ManagementService { } private sharedInterface = { - getSection: this.getSection.bind(this), + getSection: (sectionId: ManagementSectionId) => { + const section = this.getSection(sectionId); + if (!section) { + throw new Error(`Management section with id ${sectionId} is undefined`); + } + return section; + }, getSectionsEnabled: this.getSectionsEnabled.bind(this), getAllSections: this.getAllSections.bind(this), }; @@ -79,16 +89,13 @@ export class ManagementService { getStartServices ); - register({ id: 'kibana', title: 'Kibana', order: 30, euiIconType: 'logoKibana' }); - register({ - id: 'elasticsearch', - title: 'Elasticsearch', - order: 20, - euiIconType: 'logoElasticsearch', - }); + managementSections.forEach( + ({ id, title }: { id: ManagementSectionId; title: ReactElement }, idx: number) => { + register({ id, title, order: idx }); + } + ); return { - register, ...this.sharedInterface, }; } diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index 82789d3c3f55f..3e32ff4fe26b2 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -30,7 +30,6 @@ const createManagementSectionMock = (): jest.Mocked => ({ sections: { - register: jest.fn(), getSection: jest.fn().mockReturnValue(createManagementSectionMock()), getAllSections: jest.fn().mockReturnValue([]), }, diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index a8bdd5cca24a3..ecd727e8703ff 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -17,6 +17,26 @@ * under the License. */ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the 'License'); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ReactElement } from 'react'; import { IconType } from '@elastic/eui'; import { ManagementApp } from './management_app'; import { ManagementSection } from './management_section'; @@ -31,21 +51,29 @@ export interface ManagementStart { legacy: any; } +export enum ManagementSectionId { + Ingest = 'ingest', + Data = 'data', + InsightsAndAlerting = 'insightsAndAlerting', + Security = 'security', + Kibana = 'kibana', + Stack = 'stack', +} + interface SectionsServiceSetup { - getSection: (sectionId: ManagementSection['id']) => ManagementSection | undefined; + getSection: (sectionId: ManagementSectionId) => ManagementSection; getAllSections: () => ManagementSection[]; - register: RegisterSection; } interface SectionsServiceStart { - getSection: (sectionId: ManagementSection['id']) => ManagementSection | undefined; + getSection: (sectionId: ManagementSectionId) => ManagementSection; getAllSections: () => ManagementSection[]; navigateToApp: ApplicationStart['navigateToApp']; } export interface CreateSection { - id: string; - title: string; + id: ManagementSectionId; + title: string | ReactElement; order?: number; euiIconType?: string; // takes precedence over `icon` property. icon?: string; // URL to image file; fallback if no `euiIconType` diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index 28eac96dcbf46..b0c6b1952a2a5 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { ManagementSetup } from '../../management/public'; +import { ManagementSetup, ManagementSectionId } from '../../management/public'; import { DataPublicPluginStart } from '../../data/public'; import { DashboardStart } from '../../dashboard/public'; import { DiscoverStart } from '../../discover/public'; @@ -87,16 +87,13 @@ export class SavedObjectsManagementPlugin category: FeatureCatalogueCategory.ADMIN, }); - const kibanaSection = management.sections.getSection('kibana'); - if (!kibanaSection) { - throw new Error('`kibana` management section not found.'); - } + const kibanaSection = management.sections.getSection(ManagementSectionId.Kibana); kibanaSection.registerApp({ id: 'objects', title: i18n.translate('savedObjectsManagement.managementSectionLabel', { defaultMessage: 'Saved Objects', }), - order: 10, + order: 1, mount: async mountParams => { const { mountManagementSection } = await import('./management_section'); return mountManagementSection({ diff --git a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx index f3b7a19f70ae3..96297f6d51566 100644 --- a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx +++ b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx @@ -21,22 +21,17 @@ import * as React from 'react'; import ReactDOM from 'react-dom'; import { HashRouter as Router, Switch, Route, Link } from 'react-router-dom'; import { CoreSetup, Plugin } from 'kibana/public'; -import { ManagementSetup } from '../../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../../src/plugins/management/public'; export class ManagementTestPlugin implements Plugin { public setup(core: CoreSetup, { management }: { management: ManagementSetup }) { - const testSection = management.sections.register({ - id: 'test-section', - title: 'Test Section', - euiIconType: 'logoKibana', - order: 25, - }); + const testSection = management.sections.getSection(ManagementSectionId.Data); - testSection!.registerApp({ + testSection.registerApp({ id: 'test-management', title: 'Management Test', - mount(params) { + mount(params: any) { params.setBreadcrumbs([{ text: 'Management Test' }]); ReactDOM.render( @@ -63,7 +58,7 @@ export class ManagementTestPlugin }, }); - testSection! + testSection .registerApp({ id: 'test-management-disabled', title: 'Management Test Disabled', diff --git a/test/plugin_functional/test_suites/management/management_plugin.js b/test/plugin_functional/test_suites/management/management_plugin.js index 0c185f4b385b5..6ad2bb56391dd 100644 --- a/test/plugin_functional/test_suites/management/management_plugin.js +++ b/test/plugin_functional/test_suites/management/management_plugin.js @@ -40,7 +40,7 @@ export default function({ getService, getPageObjects }) { it('should redirect when app is disabled', async () => { await PageObjects.common.navigateToActualUrl( 'kibana', - 'management/test-section/test-management-disabled' + 'management/data/test-management-disabled' ); await testSubjects.existOrFail('management-landing'); }); diff --git a/x-pack/legacy/plugins/beats_management/common/constants/index.ts b/x-pack/legacy/plugins/beats_management/common/constants/index.ts index 5f9ae815e34d4..8d22b36e96d45 100644 --- a/x-pack/legacy/plugins/beats_management/common/constants/index.ts +++ b/x-pack/legacy/plugins/beats_management/common/constants/index.ts @@ -9,4 +9,4 @@ export { INDEX_NAMES } from './index_names'; export { PLUGIN } from './plugin'; export { LICENSES, REQUIRED_LICENSES, REQUIRED_ROLES } from './security'; export { TABLE_CONFIG } from './table'; -export const BASE_PATH = '/management/beats/beats_management'; +export const BASE_PATH = '/management/ingest/beats_management'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx index 75c6c79bc804a..9001eb6992a96 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx @@ -82,7 +82,7 @@ export function AlertIntegrations(props: Props) { } ), href: plugin.core.http.basePath.prepend( - '/app/kibana#/management/kibana/triggersActions/alerts' + '/app/kibana#/management/insightsAndAlerting/triggersActions/alerts' ), icon: 'tableOfContents' } diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx index 3bbd8a01d0549..c0c93bb4cb298 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx @@ -258,7 +258,7 @@ export class WatcherFlyout extends Component< )}{' '} {i18n.translate( 'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText.viewWatchLinkText', diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx index 91483b4b52d90..e1f58b7b35210 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx @@ -92,7 +92,7 @@ export class ServiceIntegrations extends React.Component { ), icon: 'watchesApp', href: core.http.basePath.prepend( - '/app/kibana#/management/elasticsearch/watcher' + '/app/kibana#/management/insightsAndAlerting/watcher' ), target: '_blank', onClick: () => this.closePopover() diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx b/x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx index c1afa433cb614..266e5a97ef07a 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx @@ -66,7 +66,7 @@ export function NoServicesMessage({ historicalDataFound, status }: Props) { defaultMessage: 'You may also have old data that needs to be migrated.' })}{' '} - + {i18n.translate('xpack.apm.servicesTable.UpgradeAssistantLink', { defaultMessage: 'Learn more by visiting the Kibana Upgrade Assistant' diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap index 227becb9a9c4f..d027422961c99 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap @@ -25,7 +25,7 @@ exports[`NoServicesMessage status: pending and historicalDataFound: false 1`] = You may also have old data that needs to be migrated. Learn more by visiting the Kibana Upgrade Assistant @@ -70,7 +70,7 @@ exports[`NoServicesMessage status: success and historicalDataFound: false 1`] = You may also have old data that needs to be migrated. Learn more by visiting the Kibana Upgrade Assistant diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap index 6d310199ba9a5..3e6be107ce3a1 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap @@ -79,7 +79,7 @@ NodeList [ Learn more by visiting the Kibana Upgrade Assistant diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceOverview/index.tsx index 99b169e3ec361..06f56d9ec1be7 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/index.tsx @@ -66,7 +66,7 @@ export function ServiceOverview() { {i18n.translate( diff --git a/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx b/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx index d2afefb83a568..96e8c754fcc5f 100644 --- a/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx @@ -17,7 +17,7 @@ interface Props { export const LicensePrompt = ({ text, showBetaBadge = false }: Props) => { const licensePageUrl = useKibanaUrl( '/app/kibana', - '/management/elasticsearch/license_management/home' + '/management/stack/license_management/home' ); const renderLicenseBody = ( diff --git a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx index 36e780f50c3ae..8ed02f039289e 100644 --- a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx +++ b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx @@ -11,7 +11,7 @@ import { useApmPluginContext } from '../../hooks/useApmPluginContext'; export function InvalidLicenseNotification() { const { core } = useApmPluginContext(); const manageLicenseURL = core.http.basePath.prepend( - '/app/kibana#/management/elasticsearch/license_management' + '/app/kibana#/management/stack/license_management' ); return ( diff --git a/x-pack/plugins/apm/public/setHelpExtension.ts b/x-pack/plugins/apm/public/setHelpExtension.ts index 1a3394651b2ff..aa23a8a2e64ea 100644 --- a/x-pack/plugins/apm/public/setHelpExtension.ts +++ b/x-pack/plugins/apm/public/setHelpExtension.ts @@ -22,7 +22,7 @@ export function setHelpExtension({ chrome, http }: CoreStart) { linkType: 'custom', href: url.format({ pathname: http.basePath.prepend('/app/kibana'), - hash: '/management/elasticsearch/upgrade_assistant' + hash: '/management/stack/upgrade_assistant' }), content: i18n.translate('xpack.apm.helpMenu.upgradeAssistantLink', { defaultMessage: 'Upgrade assistant' diff --git a/x-pack/plugins/beats_management/public/application.tsx b/x-pack/plugins/beats_management/public/application.tsx index bf450e9c7a5e3..6711e93895b62 100644 --- a/x-pack/plugins/beats_management/public/application.tsx +++ b/x-pack/plugins/beats_management/public/application.tsx @@ -19,7 +19,10 @@ import { TagsContainer } from './containers/tags'; import { FrontendLibs } from './lib/types'; import { AppRouter } from './router'; import { services } from './kbn_services'; -import { ManagementAppMountParams } from '../../../../src/plugins/management/public'; +import { + ManagementAppMountParams, + ManagementSectionId, +} from '../../../../src/plugins/management/public'; export const renderApp = ( { basePath, element, setBreadcrumbs }: ManagementAppMountParams, @@ -28,7 +31,7 @@ export const renderApp = ( ReactDOM.render( - + diff --git a/x-pack/plugins/beats_management/public/bootstrap.tsx b/x-pack/plugins/beats_management/public/bootstrap.tsx index ecca9da052fea..9a45be702e212 100644 --- a/x-pack/plugins/beats_management/public/bootstrap.tsx +++ b/x-pack/plugins/beats_management/public/bootstrap.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import { FrontendLibs } from './lib/types'; import { compose } from './lib/compose/kibana'; @@ -20,27 +19,14 @@ async function startApp(libs: FrontendLibs, core: CoreSetup) { await libs.framework.waitUntilFrameworkReady(); if (libs.framework.licenseIsAtLeast('standard')) { - libs.framework.registerManagementSection({ - id: 'beats', - name: i18n.translate('xpack.beatsManagement.centralManagementSectionLabel', { - defaultMessage: 'Beats', - }), - iconName: 'logoBeats', - }); - - libs.framework.registerManagementUI({ - sectionId: 'beats', - appId: 'beats_management', - name: i18n.translate('xpack.beatsManagement.centralManagementLinkLabel', { - defaultMessage: 'Central Management', - }), - async mount(params) { - const [coreStart, pluginsStart] = await core.getStartServices(); - setServices(coreStart, pluginsStart, params); - const { renderApp } = await import('./application'); - return renderApp(params, libs); - }, - }); + const mount = async (params: any) => { + const [coreStart, pluginsStart] = await core.getStartServices(); + setServices(coreStart, pluginsStart, params); + const { renderApp } = await import('./application'); + return renderApp(params, libs); + }; + + libs.framework.registerManagementUI(mount); } } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/adapter_types.ts index 3c0c724d3d87b..9d7a1954b60d9 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/adapter_types.ts @@ -17,19 +17,7 @@ export interface FrameworkAdapter { currentUser: FrameworkUser; // Methods waitUntilFrameworkReady(): Promise; - registerManagementSection(settings: { - id: string; - name: string; - iconName: string; - order?: number; - }): void; - registerManagementUI(settings: { - sectionId: string; - appId: string; - name: string; - order?: number; - mount: RegisterManagementAppArgs['mount']; - }): void; + registerManagementUI(mount: RegisterManagementAppArgs['mount']): void; } export const RuntimeFrameworkInfo = t.type({ diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts index d061e2f35809b..1ae21a561950d 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -9,6 +9,7 @@ import { IScope } from 'angular'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { isLeft } from 'fp-ts/lib/Either'; import { first } from 'rxjs/operators'; +import { i18n } from '@kbn/i18n'; import { SecurityPluginSetup } from '../../../../../security/public'; import { BufferedKibanaServiceCall, KibanaAdapterServiceRefs, KibanaUIConfig } from '../../types'; import { @@ -21,6 +22,7 @@ import { import { ManagementSetup, RegisterManagementAppArgs, + ManagementSectionId, } from '../../../../../../../src/plugins/management/public'; import { LicensingPluginSetup } from '../../../../../licensing/public'; import { BeatsManagementConfigType } from '../../../../common'; @@ -102,40 +104,15 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { } } - public registerManagementSection(settings: { - id: string; - name: string; - iconName: string; - order?: number; - }) { - this.management.sections.register({ - id: settings.id, - title: settings.name, - euiIconType: settings.iconName, - order: settings.order || 30, - }); - } - - public registerManagementUI(settings: { - sectionId: string; - appId: string; - name: string; - order?: number; - mount: RegisterManagementAppArgs['mount']; - }) { - const section = this.management.sections.getSection(settings.sectionId); - - if (!section) { - throw new Error( - `registerManagementUI was called with a sectionId of ${settings.sectionId}, and that is is not yet regestered as a section` - ); - } - + public registerManagementUI(mount: RegisterManagementAppArgs['mount']) { + const section = this.management.sections.getSection(ManagementSectionId.Ingest); section.registerApp({ - id: settings.appId, - title: settings.name, - order: settings.order || 30, - mount: settings.mount, + id: 'beats_management', + title: i18n.translate('xpack.beatsManagement.centralManagementLinkLabel', { + defaultMessage: 'Beats Central Management', + }), + order: 2, + mount, }); } } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/testing_framework_adapter.ts index 56f428ff0b927..52d185e0d7dc9 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/testing_framework_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/testing_framework_adapter.ts @@ -39,15 +39,6 @@ export class TestingFrameworkAdapter implements FrameworkAdapter { return; } - public registerManagementSection(settings: { - id?: string; - name: string; - iconName: string; - order?: number; - }) { - throw new Error('not yet implamented'); - } - public registerManagementUI(settings: { sectionId?: string; name: string; order?: number }) { throw new Error('not yet implamented'); } diff --git a/x-pack/plugins/beats_management/public/lib/framework.ts b/x-pack/plugins/beats_management/public/lib/framework.ts index c850bdd8bc0ee..9e4271c683415 100644 --- a/x-pack/plugins/beats_management/public/lib/framework.ts +++ b/x-pack/plugins/beats_management/public/lib/framework.ts @@ -13,7 +13,6 @@ import { FrameworkAdapter } from './adapters/framework/adapter_types'; export class FrameworkLib { public waitUntilFrameworkReady = this.adapter.waitUntilFrameworkReady.bind(this.adapter); - public registerManagementSection = this.adapter.registerManagementSection.bind(this.adapter); public registerManagementUI = this.adapter.registerManagementUI.bind(this.adapter); constructor(private readonly adapter: FrameworkAdapter) {} diff --git a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts index 797141b0996af..96884cf4bead8 100644 --- a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts +++ b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts @@ -24,8 +24,8 @@ export const APPS = { }; export const MANAGEMENT_ID = 'cross_cluster_replication'; -export const BASE_PATH = `/management/elasticsearch/${MANAGEMENT_ID}`; -export const BASE_PATH_REMOTE_CLUSTERS = '/management/elasticsearch/remote_clusters'; +export const BASE_PATH = `/management/data/${MANAGEMENT_ID}`; +export const BASE_PATH_REMOTE_CLUSTERS = '/management/data/remote_clusters'; export const API_BASE_PATH = '/api/cross_cluster_replication'; export const API_REMOTE_CLUSTERS_BASE_PATH = '/api/remote_clusters'; export const API_INDEX_MANAGEMENT_BASE_PATH = '/api/index_management'; diff --git a/x-pack/plugins/cross_cluster_replication/public/plugin.ts b/x-pack/plugins/cross_cluster_replication/public/plugin.ts index dfe9e4e657c30..561da838a4202 100644 --- a/x-pack/plugins/cross_cluster_replication/public/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/public/plugin.ts @@ -9,6 +9,7 @@ import { get } from 'lodash'; import { first } from 'rxjs/operators'; import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; +import { ManagementSectionId } from '../../../../src/plugins/management/public'; import { PLUGIN, MANAGEMENT_ID } from '../common/constants'; import { init as initUiMetric } from './app/services/track_ui_metric'; import { init as initNotification } from './app/services/notifications'; @@ -22,7 +23,7 @@ export class CrossClusterReplicationPlugin implements Plugin { public setup(coreSetup: CoreSetup, plugins: PluginDependencies) { const { licensing, remoteClusters, usageCollection, management, indexManagement } = plugins; - const esSection = management.sections.getSection('elasticsearch'); + const esSection = management.sections.getSection(ManagementSectionId.Data); const { http, @@ -36,7 +37,7 @@ export class CrossClusterReplicationPlugin implements Plugin { initUiMetric(usageCollection); initNotification(toasts, fatalErrors); - const ccrApp = esSection!.registerApp({ + const ccrApp = esSection.registerApp({ id: MANAGEMENT_ID, title: PLUGIN.TITLE, order: 6, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap index dbdbe2b52bd56..d64c8c6239fcd 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap @@ -364,11 +364,11 @@ exports[`extend index management ilm summary extension should return extension w className="euiDescriptionList__description" > testy @@ -748,11 +748,11 @@ exports[`extend index management ilm summary extension should return extension w className="euiDescriptionList__description" > testy diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap index cbc735bb150f5..857a63826505e 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap @@ -94,7 +94,7 @@ exports[`policy table should show empty state when there are not any policies 1` { const [coreStart] = await getStartServices(); const { diff --git a/x-pack/plugins/index_management/common/constants/base_path.ts b/x-pack/plugins/index_management/common/constants/base_path.ts index 3a49e2870b609..c17d96250b1fb 100644 --- a/x-pack/plugins/index_management/common/constants/base_path.ts +++ b/x-pack/plugins/index_management/common/constants/base_path.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const BASE_PATH = '/management/elasticsearch/index_management/'; +export const BASE_PATH = '/management/data/index_management/'; diff --git a/x-pack/plugins/index_management/public/application/services/navigation.ts b/x-pack/plugins/index_management/public/application/services/navigation.ts index e56e8d474d780..8bd9fc2432232 100644 --- a/x-pack/plugins/index_management/public/application/services/navigation.ts +++ b/x-pack/plugins/index_management/public/application/services/navigation.ts @@ -19,8 +19,6 @@ export const getIndexListUri = (filter: any) => { export const getILMPolicyPath = (policyName: string) => { return encodeURI( - `#/management/elasticsearch/index_lifecycle_management/policies/edit/${encodeURIComponent( - policyName - )}` + `#/management/data/index_lifecycle_management/policies/edit/${encodeURIComponent(policyName)}` ); }; diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 78e80687abeb4..5fb8ce7207729 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup } from '../../../../src/core/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { UIM_APP_NAME, PLUGIN } from '../common/constants'; import { httpService } from './application/services/http'; @@ -48,10 +48,10 @@ export class IndexMgmtUIPlugin { notificationService.setup(notifications); this.uiMetricService.setup(usageCollection); - management.sections.getSection('elasticsearch')!.registerApp({ + management.sections.getSection(ManagementSectionId.Data).registerApp({ id: PLUGIN.id, title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }), - order: 2, + order: 0, mount: async params => { const { mountManagementSection } = await import('./application/mount_management_section'); const services = { diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx index 8bcf0e9ed5be5..bafb38459b17b 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx @@ -35,7 +35,7 @@ export const MetricsAlertDropdown = () => { icon="tableOfContents" key="manageLink" href={kibana.services?.application?.getUrlForApp( - 'kibana#/management/kibana/triggersActions/alerts' + 'kibana#/management/insightsAndAlerting/triggersActions/alerts' )} > diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx index d2904206875c7..a3cebcf33f386 100644 --- a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx @@ -35,7 +35,7 @@ export const InventoryAlertDropdown = () => { icon="tableOfContents" key="manageLink" href={kibana.services?.application?.getUrlForApp( - 'kibana#/management/kibana/triggersActions/alerts' + 'kibana#/management/insightsAndAlerting/triggersActions/alerts' )} > diff --git a/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx index dd888639b6d07..d808b4f3b64aa 100644 --- a/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx @@ -16,7 +16,7 @@ export const AlertDropdown = () => { const manageAlertsLinkProps = useLinkProps( { app: 'kibana', - hash: 'management/kibana/triggersActions/alerts', + hash: 'management/insightsAndAlerting/triggersActions/alerts', }, { hrefOnly: true, diff --git a/x-pack/plugins/ingest_pipelines/common/constants.ts b/x-pack/plugins/ingest_pipelines/common/constants.ts index edf681c276a84..de291e364e02f 100644 --- a/x-pack/plugins/ingest_pipelines/common/constants.ts +++ b/x-pack/plugins/ingest_pipelines/common/constants.ts @@ -11,7 +11,7 @@ export const PLUGIN_ID = 'ingest_pipelines'; export const PLUGIN_MIN_LICENSE_TYPE = basicLicense; -export const BASE_PATH = '/management/elasticsearch/ingest_pipelines'; +export const BASE_PATH = '/management/ingest/ingest_pipelines'; export const API_BASE_PATH = '/api/ingest_pipelines'; diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts index 0ab46f386e83b..d537f8d68d7e4 100644 --- a/x-pack/plugins/ingest_pipelines/public/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin } from 'src/core/public'; +import { ManagementSectionId } from '../../../../src/plugins/management/public'; import { PLUGIN_ID } from '../common/constants'; import { uiMetricService, apiService } from './application/services'; import { Dependencies } from './types'; @@ -20,7 +21,7 @@ export class IngestPipelinesPlugin implements Plugin { uiMetricService.setup(usageCollection); apiService.setup(http, uiMetricService); - management.sections.getSection('elasticsearch')!.registerApp({ + management.sections.getSection(ManagementSectionId.Ingest).registerApp({ id: PLUGIN_ID, order: 1, title: i18n.translate('xpack.ingestPipelines.appTitle', { diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap index 03421e66c77f5..e4411807dfa56 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AddLicense component when license is active should display correct verbiage 1`] = `""`; +exports[`AddLicense component when license is active should display correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; -exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; +exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index 9ac8b14236685..b621e89efbee3 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -958,11 +958,11 @@ exports[`UploadLicense should display a modal when license requires acknowledgem className="euiFlexItem euiFlexItem--flexGrowZero" > { const [core] = await getStartServices(); const initialLicense = await plugins.licensing.license$.pipe(first()).toPromise(); diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index f68e1dcfaf62b..9f0019680d14b 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -367,7 +367,7 @@ describe('licensing plugin', () => { expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1); expect(mountExpiredBannerMock).toHaveBeenCalledWith({ type: 'gold', - uploadUrl: '/app/kibana#/management/elasticsearch/license_management/upload_license', + uploadUrl: '/app/kibana#/management/stack/license_management/upload_license', }); }); }); diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index dab4c4048ce4c..31910d81b3434 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -148,7 +148,7 @@ export class LicensingPlugin implements Plugin { private showExpiredBanner(license: ILicense) { const uploadUrl = this.coreStart!.http.basePath.prepend( - '/app/kibana#/management/elasticsearch/license_management/upload_license' + '/app/kibana#/management/stack/license_management/upload_license' ); this.coreStart!.overlays.banners.add( mountExpiredBanner({ diff --git a/x-pack/plugins/logstash/public/application/breadcrumbs.js b/x-pack/plugins/logstash/public/application/breadcrumbs.js index 322b9860b3785..4ef259b84e24f 100644 --- a/x-pack/plugins/logstash/public/application/breadcrumbs.js +++ b/x-pack/plugins/logstash/public/application/breadcrumbs.js @@ -12,7 +12,7 @@ export function getPipelineListBreadcrumbs() { text: i18n.translate('xpack.logstash.pipelines.listBreadcrumb', { defaultMessage: 'Pipelines', }), - href: '#/management/logstash/pipelines', + href: '#/management/ingest/pipelines', }, ]; } diff --git a/x-pack/plugins/logstash/public/plugin.ts b/x-pack/plugins/logstash/public/plugin.ts index 7fbed5b3b8602..7d4afc0a4ea13 100644 --- a/x-pack/plugins/logstash/public/plugin.ts +++ b/x-pack/plugins/logstash/public/plugin.ts @@ -14,8 +14,8 @@ import { HomePublicPluginSetup, FeatureCatalogueCategory, } from '../../../../src/plugins/home/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { LicensingPluginSetup } from '../../licensing/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; // @ts-ignore import { LogstashLicenseService } from './services'; @@ -34,26 +34,23 @@ export class LogstashPlugin implements Plugin { const logstashLicense$ = plugins.licensing.license$.pipe( map(license => new LogstashLicenseService(license)) ); - const section = plugins.management.sections.register({ - id: 'logstash', - title: 'Logstash', - order: 30, - euiIconType: 'logoLogstash', - }); - const managementApp = section.registerApp({ - id: 'pipelines', - title: i18n.translate('xpack.logstash.managementSection.pipelinesTitle', { - defaultMessage: 'Pipelines', - }), - order: 10, - mount: async params => { - const [coreStart] = await core.getStartServices(); - const { renderApp } = await import('./application'); - const isMonitoringEnabled = 'monitoring' in plugins; - return renderApp(coreStart, params, isMonitoringEnabled, logstashLicense$); - }, - }); + const managementApp = plugins.management.sections + .getSection(ManagementSectionId.Ingest) + .registerApp({ + id: 'pipelines', + title: i18n.translate('xpack.logstash.managementSection.pipelinesTitle', { + defaultMessage: 'Logstash Pipelines', + }), + order: 1, + mount: async params => { + const [coreStart] = await core.getStartServices(); + const { renderApp } = await import('./application'); + const isMonitoringEnabled = 'monitoring' in plugins; + + return renderApp(coreStart, params, isMonitoringEnabled, logstashLicense$); + }, + }); this.licenseSubscription = logstashLicense$.subscribe((license: any) => { if (license.enableLinks) { @@ -74,7 +71,7 @@ export class LogstashPlugin implements Plugin { defaultMessage: 'Create, delete, update, and clone data ingestion pipelines.', }), icon: 'pipelineApp', - path: '/app/kibana#/management/logstash/pipelines', + path: '/app/kibana#/management/ingest/pipelines', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx index 2d6505f5ce1f7..126fd25a536f6 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx @@ -184,7 +184,7 @@ export const DatavisualizerSelector: FC = () => { footer={ = ({ /> } description="" - href={`${basePath.get()}/app/kibana#/management/elasticsearch/index_management/indices/filter/${index}`} + href={`${basePath.get()}/app/kibana#/management/data/index_management/indices/filter/${index}`} /> diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js index 2a65ee06f2c2c..307fa79f5dea2 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js @@ -167,7 +167,7 @@ class CreateWatchService { saveWatch(watchModel) .then(() => { this.status.watch = this.STATUS.SAVED; - this.config.watcherEditURL = `${basePath.get()}/app/kibana#/management/elasticsearch/watcher/watches/watch/${id}/edit?_g=()`; + this.config.watcherEditURL = `${basePath.get()}/app/kibana#/management/insightsAndAlerting/watcher/watches/watch/${id}/edit?_g=()`; resolve({ id, url: this.config.watcherEditURL, diff --git a/x-pack/plugins/ml/public/application/management/index.ts b/x-pack/plugins/ml/public/application/management/index.ts index 6bc5c9b15074f..f15cdb12afb21 100644 --- a/x-pack/plugins/ml/public/application/management/index.ts +++ b/x-pack/plugins/ml/public/application/management/index.ts @@ -16,7 +16,11 @@ import { take } from 'rxjs/operators'; import { CoreSetup } from 'kibana/public'; import { MlStartDependencies, MlSetupDependencies } from '../../plugin'; -import { PLUGIN_ID, PLUGIN_ICON } from '../../../common/constants/app'; +import { + ManagementAppMountParams, + ManagementSectionId, +} from '../../../../../../src/plugins/management/public'; +import { PLUGIN_ID } from '../../../common/constants/app'; import { MINIMUM_FULL_LICENSE } from '../../../common/license'; export function initManagementSection( @@ -30,22 +34,13 @@ export function initManagementSection( management !== undefined && license.check(PLUGIN_ID, MINIMUM_FULL_LICENSE).state === 'valid' ) { - const mlSection = management.sections.register({ - id: PLUGIN_ID, - title: i18n.translate('xpack.ml.management.mlTitle', { - defaultMessage: 'Machine Learning', - }), - order: 100, - icon: PLUGIN_ICON, - }); - - mlSection.registerApp({ + management.sections.getSection(ManagementSectionId.InsightsAndAlerting).registerApp({ id: 'jobsListLink', title: i18n.translate('xpack.ml.management.jobsListTitle', { - defaultMessage: 'Jobs list', + defaultMessage: 'Machine Learning Jobs', }), - order: 10, - async mount(params) { + order: 2, + async mount(params: ManagementAppMountParams) { const { mountApp } = await import('./jobs_list'); return mountApp(core, params); }, diff --git a/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx b/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx index 3e4e9cfbd2b66..87a7156b6f52e 100644 --- a/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx @@ -42,7 +42,7 @@ export const OverviewSideBar: FC = ({ createAnomalyDetectionJobDisabled } const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; const docsLink = `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/xpack-ml.html`; - const transformsLink = `${basePath.get()}/app/kibana#/management/elasticsearch/transform`; + const transformsLink = `${basePath.get()}/app/kibana#/management/data/transform`; return ( diff --git a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js index feda891c1ce29..69d7727f9a20a 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js @@ -288,7 +288,7 @@ const handleClickIncompatibleLicense = (scope, clusterName) => { }; const handleClickInvalidLicense = (scope, clusterName) => { - const licensingPath = `${Legacy.shims.getBasePath()}/app/kibana#/management/elasticsearch/license_management/home`; + const licensingPath = `${Legacy.shims.getBasePath()}/app/kibana#/management/stack/license_management/home`; licenseWarning(scope, { title: toMountPoint( diff --git a/x-pack/plugins/monitoring/public/components/license/index.js b/x-pack/plugins/monitoring/public/components/license/index.js index 085cc9082cf53..e8ea1f8df227a 100644 --- a/x-pack/plugins/monitoring/public/components/license/index.js +++ b/x-pack/plugins/monitoring/public/components/license/index.js @@ -169,7 +169,7 @@ const LicenseUpdateInfoForRemote = ({ isPrimaryCluster }) => { export function License(props) { const { status, type, isExpired, expiryDate } = props; - const licenseManagement = `${Legacy.shims.getBasePath()}/app/kibana#/management/elasticsearch/license_management`; + const licenseManagement = `${Legacy.shims.getBasePath()}/app/kibana#/management/stack/license_management`; return ( diff --git a/x-pack/plugins/remote_clusters/public/application/constants/paths.ts b/x-pack/plugins/remote_clusters/public/application/constants/paths.ts index 23fe6758542c9..770447ce33f93 100644 --- a/x-pack/plugins/remote_clusters/public/application/constants/paths.ts +++ b/x-pack/plugins/remote_clusters/public/application/constants/paths.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CRUD_APP_BASE_PATH: string = '/management/elasticsearch/remote_clusters'; +export const CRUD_APP_BASE_PATH: string = '/management/data/remote_clusters'; diff --git a/x-pack/plugins/remote_clusters/public/plugin.ts b/x-pack/plugins/remote_clusters/public/plugin.ts index 22f98e94748d8..fde8ffa511319 100644 --- a/x-pack/plugins/remote_clusters/public/plugin.ts +++ b/x-pack/plugins/remote_clusters/public/plugin.ts @@ -6,6 +6,8 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin, CoreStart, PluginInitializerContext } from 'kibana/public'; + +import { ManagementSectionId } from '../../../../src/plugins/management/public'; import { init as initBreadcrumbs } from './application/services/breadcrumb'; import { init as initDocumentation } from './application/services/documentation'; import { init as initHttp } from './application/services/http'; @@ -31,13 +33,14 @@ export class RemoteClustersUIPlugin } = this.initializerContext.config.get(); if (isRemoteClustersUiEnabled) { - const esSection = management.sections.getSection('elasticsearch'); + const esSection = management.sections.getSection(ManagementSectionId.Data); - esSection!.registerApp({ + esSection.registerApp({ id: 'remote_clusters', title: i18n.translate('xpack.remoteClusters.appTitle', { defaultMessage: 'Remote Clusters', }), + order: 7, mount: async ({ element, setBreadcrumbs }) => { const [core] = await getStartServices(); const { diff --git a/x-pack/plugins/reporting/public/plugin.tsx b/x-pack/plugins/reporting/public/plugin.tsx index 66366cc0b520d..f600b1ebbb96c 100644 --- a/x-pack/plugins/reporting/public/plugin.tsx +++ b/x-pack/plugins/reporting/public/plugin.tsx @@ -17,9 +17,9 @@ import { Plugin, PluginInitializerContext, } from 'src/core/public'; -import { ManagementSetup } from 'src/plugins/management/public'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; import { JobId, JobStatusBuckets } from '../'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { CONTEXT_MENU_TRIGGER } from '../../../../src/plugins/embeddable/public'; import { FeatureCatalogueCategory, @@ -117,10 +117,10 @@ export class ReportingPublicPlugin implements Plugin { category: FeatureCatalogueCategory.ADMIN, }); - management.sections.getSection('kibana')!.registerApp({ + management.sections.getSection(ManagementSectionId.InsightsAndAlerting).registerApp({ id: 'reporting', title: this.title, - order: 15, + order: 1, mount: async params => { const [start] = await getStartServices(); params.setBreadcrumbs([{ text: this.breadcrumbText }]); diff --git a/x-pack/plugins/rollup/public/crud_app/constants/paths.js b/x-pack/plugins/rollup/public/crud_app/constants/paths.js index 83a7ca6bc5967..44829f38e79cd 100644 --- a/x-pack/plugins/rollup/public/crud_app/constants/paths.js +++ b/x-pack/plugins/rollup/public/crud_app/constants/paths.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CRUD_APP_BASE_PATH = '/management/elasticsearch/rollup_jobs'; +export const CRUD_APP_BASE_PATH = '/management/data/rollup_jobs'; diff --git a/x-pack/plugins/rollup/public/plugin.ts b/x-pack/plugins/rollup/public/plugin.ts index 0e0333cf30f17..b2e793d7e75e9 100644 --- a/x-pack/plugins/rollup/public/plugin.ts +++ b/x-pack/plugins/rollup/public/plugin.ts @@ -18,7 +18,7 @@ import { } from '../../../../src/plugins/home/public'; // @ts-ignore import { CRUD_APP_BASE_PATH } from './crud_app/constants'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { IndexManagementPluginSetup } from '../../index_management/public'; import { IndexPatternManagementSetup } from '../../../../src/plugins/index_pattern_management/public'; // @ts-ignore @@ -77,26 +77,23 @@ export class RollupPlugin implements Plugin { }); } - const esSection = management.sections.getSection('elasticsearch'); - if (esSection) { - esSection.registerApp({ - id: 'rollup_jobs', - title: i18n.translate('xpack.rollupJobs.appTitle', { defaultMessage: 'Rollup Jobs' }), - order: 5, - async mount(params) { - params.setBreadcrumbs([ - { - text: i18n.translate('xpack.rollupJobs.breadcrumbsTitle', { - defaultMessage: 'Rollup Jobs', - }), - }, - ]); - const { renderApp } = await import('./application'); + management.sections.getSection(ManagementSectionId.Data).registerApp({ + id: 'rollup_jobs', + title: i18n.translate('xpack.rollupJobs.appTitle', { defaultMessage: 'Rollup Jobs' }), + order: 4, + async mount(params) { + params.setBreadcrumbs([ + { + text: i18n.translate('xpack.rollupJobs.breadcrumbsTitle', { + defaultMessage: 'Rollup Jobs', + }), + }, + ]); + const { renderApp } = await import('./application'); - return renderApp(core, params); - }, - }); - } + return renderApp(core, params); + }, + }); } start(core: CoreStart) { diff --git a/x-pack/plugins/security/public/management/management_service.test.ts b/x-pack/plugins/security/public/management/management_service.test.ts index 53c12ad7ab12c..466e1aa9b3c68 100644 --- a/x-pack/plugins/security/public/management/management_service.test.ts +++ b/x-pack/plugins/security/public/management/management_service.test.ts @@ -27,9 +27,8 @@ describe('ManagementService', () => { const mockSection = { registerApp: jest.fn() }; const managementSetup = { sections: { - getSection: jest.fn(), + getSection: jest.fn().mockReturnValue(mockSection), getAllSections: jest.fn(), - register: jest.fn().mockReturnValue(mockSection), }, }; @@ -42,14 +41,6 @@ describe('ManagementService', () => { management: managementSetup, }); - expect(managementSetup.sections.register).toHaveBeenCalledTimes(1); - expect(managementSetup.sections.register).toHaveBeenCalledWith({ - id: 'security', - title: 'Security', - order: 100, - euiIconType: 'securityApp', - }); - expect(mockSection.registerApp).toHaveBeenCalledTimes(4); expect(mockSection.registerApp).toHaveBeenCalledWith({ id: 'users', @@ -96,9 +87,8 @@ describe('ManagementService', () => { authc: securityMock.createSetup().authc, management: { sections: { - getSection: jest.fn(), + getSection: jest.fn().mockReturnValue({ registerApp: jest.fn() }), getAllSections: jest.fn(), - register: jest.fn().mockReturnValue({ registerApp: jest.fn() }), }, }, }); diff --git a/x-pack/plugins/security/public/management/management_service.ts b/x-pack/plugins/security/public/management/management_service.ts index 7c4c470730ffe..2dc3ecbd9f3a2 100644 --- a/x-pack/plugins/security/public/management/management_service.ts +++ b/x-pack/plugins/security/public/management/management_service.ts @@ -5,12 +5,12 @@ */ import { Subscription } from 'rxjs'; -import { i18n } from '@kbn/i18n'; import { StartServicesAccessor, FatalErrorsSetup } from 'src/core/public'; import { ManagementApp, ManagementSetup, ManagementStart, + ManagementSectionId, } from '../../../../../src/plugins/management/public'; import { SecurityLicense } from '../../common/licensing'; import { AuthenticationServiceSetup } from '../authentication'; @@ -39,14 +39,7 @@ export class ManagementService { setup({ getStartServices, management, authc, license, fatalErrors }: SetupParams) { this.license = license; - const securitySection = management.sections.register({ - id: 'security', - title: i18n.translate('xpack.security.management.securityTitle', { - defaultMessage: 'Security', - }), - order: 100, - euiIconType: 'securityApp', - }); + const securitySection = management.sections.getSection(ManagementSectionId.Security); securitySection.registerApp(usersManagementApp.create({ authc, getStartServices })); securitySection.registerApp( @@ -58,7 +51,7 @@ export class ManagementService { start({ management }: StartParams) { this.licenseFeaturesSubscription = this.license.features$.subscribe(async features => { - const securitySection = management.sections.getSection('security')!; + const securitySection = management.sections.getSection(ManagementSectionId.Security); const securityManagementAppsStatuses: Array<[ManagementApp, boolean]> = [ [securitySection.getApp(usersManagementApp.id)!, features.showLinks], diff --git a/x-pack/plugins/siem/public/alerts/components/rules/select_rule_type/index.tsx b/x-pack/plugins/siem/public/alerts/components/rules/select_rule_type/index.tsx index 58112732bea3b..4a21eb0fbcf23 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/select_rule_type/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/select_rule_type/index.tsx @@ -77,7 +77,7 @@ export const SelectRuleType: React.FC = ({ const setQuery = useCallback(() => setType('query'), [setType]); const mlCardDisabled = isReadOnly || !hasValidLicense || !isMlAdmin; const licensingUrl = useKibana().services.application.getUrlForApp('kibana', { - path: '#/management/elasticsearch/license_management', + path: '#/management/stack/license_management', }); return ( diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap index 113612200d367..87cf9fb18adf3 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap +++ b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap @@ -50,7 +50,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` grow={false} > diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx b/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx index 5a483e0d5b876..eda6b0f28499c 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx +++ b/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx @@ -59,7 +59,7 @@ export const UpgradeContentsComponent = () => ( diff --git a/x-pack/plugins/snapshot_restore/public/application/constants/index.ts b/x-pack/plugins/snapshot_restore/public/application/constants/index.ts index ea6f5c80b9343..ad95c19870d15 100644 --- a/x-pack/plugins/snapshot_restore/public/application/constants/index.ts +++ b/x-pack/plugins/snapshot_restore/public/application/constants/index.ts @@ -6,7 +6,7 @@ import { DAY } from '../../shared_imports'; -export const BASE_PATH = '/management/elasticsearch/snapshot_restore'; +export const BASE_PATH = '/management/data/snapshot_restore'; export const DEFAULT_SECTION: Section = 'snapshots'; export type Section = 'repositories' | 'snapshots' | 'restore_status' | 'policies'; diff --git a/x-pack/plugins/snapshot_restore/public/plugin.ts b/x-pack/plugins/snapshot_restore/public/plugin.ts index d966d0c32651c..13a15c5711ba9 100644 --- a/x-pack/plugins/snapshot_restore/public/plugin.ts +++ b/x-pack/plugins/snapshot_restore/public/plugin.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, PluginInitializerContext } from 'src/core/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { PLUGIN } from '../common/constants'; import { ClientConfigType } from './types'; @@ -40,12 +40,12 @@ export class SnapshotRestoreUIPlugin { textService.setup(i18n); httpService.setup(http); - management.sections.getSection('elasticsearch')!.registerApp({ + management.sections.getSection(ManagementSectionId.Data).registerApp({ id: PLUGIN.id, title: i18n.translate('xpack.snapshotRestore.appTitle', { defaultMessage: 'Snapshot and Restore', }), - order: 7, + order: 3, mount: async params => { const { mountManagementSection } = await import('./application/mount_management_section'); const services = { diff --git a/x-pack/plugins/spaces/public/management/management_service.test.ts b/x-pack/plugins/spaces/public/management/management_service.test.ts index 782c261be9664..7f8acccf71d88 100644 --- a/x-pack/plugins/spaces/public/management/management_service.test.ts +++ b/x-pack/plugins/spaces/public/management/management_service.test.ts @@ -39,7 +39,7 @@ describe('ManagementService', () => { expect(mockKibanaSection.registerApp).toHaveBeenCalledWith({ id: 'spaces', title: 'Spaces', - order: 10, + order: 2, mount: expect.any(Function), }); }); diff --git a/x-pack/plugins/spaces/public/management/management_service.tsx b/x-pack/plugins/spaces/public/management/management_service.tsx index cec4bee1373ca..297df07636849 100644 --- a/x-pack/plugins/spaces/public/management/management_service.tsx +++ b/x-pack/plugins/spaces/public/management/management_service.tsx @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ManagementSetup, ManagementApp } from 'src/plugins/management/public'; import { StartServicesAccessor, Capabilities } from 'src/core/public'; +import { + ManagementSetup, + ManagementApp, + ManagementSectionId, +} from '../../../../../src/plugins/management/public'; import { SecurityLicense } from '../../../security/public'; import { SpacesManager } from '../spaces_manager'; import { PluginsStart } from '../plugin'; @@ -25,12 +29,11 @@ export class ManagementService { private registeredSpacesManagementApp?: ManagementApp; public setup({ getStartServices, management, spacesManager, securityLicense }: SetupDeps) { - const kibanaSection = management.sections.getSection('kibana'); - if (kibanaSection) { - this.registeredSpacesManagementApp = kibanaSection.registerApp( + this.registeredSpacesManagementApp = management.sections + .getSection(ManagementSectionId.Kibana) + .registerApp( spacesManagementApp.create({ getStartServices, spacesManager, securityLicense }) ); - } } public start({ capabilities }: StartDeps) { diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 61c872ec9269c..92c78d63d1b2e 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -70,7 +70,7 @@ describe('spacesManagementApp', () => { Object { "id": "spaces", "mount": [Function], - "order": 10, + "order": 2, "title": "Spaces", } `); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index 92b369807b0da..079cf2234b13b 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -28,7 +28,7 @@ export const spacesManagementApp = Object.freeze({ create({ getStartServices, spacesManager, securityLicense }: CreateParams) { return { id: this.id, - order: 10, + order: 2, title: i18n.translate('xpack.spaces.displayName', { defaultMessage: 'Spaces', }), diff --git a/x-pack/plugins/spaces/public/plugin.test.ts b/x-pack/plugins/spaces/public/plugin.test.ts index d879a318fe815..a98f593f546a0 100644 --- a/x-pack/plugins/spaces/public/plugin.test.ts +++ b/x-pack/plugins/spaces/public/plugin.test.ts @@ -7,7 +7,7 @@ import { coreMock } from 'src/core/public/mocks'; import { SpacesPlugin } from './plugin'; import { homePluginMock } from '../../../../src/plugins/home/public/mocks'; -import { ManagementSection } from '../../../../src/plugins/management/public'; +import { ManagementSection, ManagementSectionId } from '../../../../src/plugins/management/public'; import { managementPluginMock } from '../../../../src/plugins/management/public/mocks'; import { advancedSettingsMock } from '../../../../src/plugins/advanced_settings/public/mocks'; import { featuresPluginMock } from '../../features/public/mocks'; @@ -35,7 +35,7 @@ describe('Spaces plugin', () => { const kibanaSection = new ManagementSection( { - id: 'kibana', + id: ManagementSectionId.Kibana, title: 'Mock Kibana Section', order: 1, }, diff --git a/x-pack/plugins/transform/public/app/constants/index.ts b/x-pack/plugins/transform/public/app/constants/index.ts index 5d71980c83714..3156fae7545b1 100644 --- a/x-pack/plugins/transform/public/app/constants/index.ts +++ b/x-pack/plugins/transform/public/app/constants/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CLIENT_BASE_PATH = '/management/elasticsearch/transform/'; +export const CLIENT_BASE_PATH = '/management/data/transform/'; export enum SECTION_SLUG { HOME = 'transform_management', diff --git a/x-pack/plugins/transform/public/plugin.ts b/x-pack/plugins/transform/public/plugin.ts index 563a569fe95b5..1411f80cecd2e 100644 --- a/x-pack/plugins/transform/public/plugin.ts +++ b/x-pack/plugins/transform/public/plugin.ts @@ -7,8 +7,8 @@ import { i18n as kbnI18n } from '@kbn/i18n'; import { CoreSetup } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { ManagementSetup } from 'src/plugins/management/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { registerFeature } from './register_feature'; export interface PluginsDependencies { @@ -22,20 +22,18 @@ export class TransformUiPlugin { const { management, home } = pluginsSetup; // Register management section - const esSection = management.sections.getSection('elasticsearch'); - if (esSection !== undefined) { - esSection.registerApp({ - id: 'transform', - title: kbnI18n.translate('xpack.transform.appTitle', { - defaultMessage: 'Transforms', - }), - order: 4, - mount: async params => { - const { mountManagementSection } = await import('./app/mount_management_section'); - return mountManagementSection(coreSetup, params); - }, - }); - } + const esSection = management.sections.getSection(ManagementSectionId.Data); + esSection.registerApp({ + id: 'transform', + title: kbnI18n.translate('xpack.transform.appTitle', { + defaultMessage: 'Transforms', + }), + order: 5, + mount: async params => { + const { mountManagementSection } = await import('./app/mount_management_section'); + return mountManagementSection(coreSetup, params); + }, + }); registerFeature(home); } diff --git a/x-pack/plugins/transform/public/register_feature.ts b/x-pack/plugins/transform/public/register_feature.ts index 708dfcb70c67a..c81a18a3def87 100644 --- a/x-pack/plugins/transform/public/register_feature.ts +++ b/x-pack/plugins/transform/public/register_feature.ts @@ -22,7 +22,7 @@ export const registerFeature = (home: HomePublicPluginSetup) => { 'Use transforms to pivot existing Elasticsearch indices into summarized or entity-centric indices.', }), icon: 'managementApp', // there is currently no Transforms icon, so using the general management app icon - path: '/app/kibana#/management/elasticsearch/transform', + path: '/app/kibana#/management/data/transform', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index bc4a1ace2967b..219e922a0158c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2419,7 +2419,6 @@ "kibana-react.tableListView.listing.table.editActionDescription": "編集", "kibana-react.tableListView.listing.table.editActionName": "編集", "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "{entityName} を削除できません", - "management.connectDataDisplayName": "データに接続", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", "indexPatternManagement.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", @@ -4759,8 +4758,6 @@ "xpack.beatsManagement.breadcrumb.beatTags": "{beatId} のビートタグ", "xpack.beatsManagement.breadcrumb.configurationTags": "構成タグ", "xpack.beatsManagement.breadcrumb.enrolledBeats": "登録済みのビート", - "xpack.beatsManagement.centralManagementLinkLabel": "集中管理", - "xpack.beatsManagement.centralManagementSectionLabel": "ビート", "xpack.beatsManagement.config.other.error": "有効な YAML フォーマットを使用してください", "xpack.beatsManagement.config.otherConfigDescription": "YAML フォーマットで Filebeat インプットの他の設定を指定します", "xpack.beatsManagement.config.otherConfigLabel": "他の構成", @@ -9975,7 +9972,6 @@ "xpack.ml.management.jobsList.noGrantedPrivilegesDescription": "ML ジョブを管理するパーミッションがありません", "xpack.ml.management.jobsList.noPermissionToAccessLabel": "ML ジョブへのアクセスにはパーミッションが必要です", "xpack.ml.management.jobsListTitle": "ジョブリスト", - "xpack.ml.management.mlTitle": "機械学習", "xpack.ml.messagebarService.errorTitle": "エラーが発生しました", "xpack.ml.models.jobService.allOtherRequestsCancelledDescription": " 他のすべてのリクエストはキャンセルされました。", "xpack.ml.models.jobService.categorization.messages.failureToGetTokens": "フィールド値の例のサンプルをトークン化することができませんでした。{message}", @@ -12827,7 +12823,6 @@ "xpack.security.management.roles.statusColumnName": "ステータス", "xpack.security.management.roles.subtitle": "ユーザーのグループにロールを適用してスタック全体のパーミッションを管理", "xpack.security.management.rolesTitle": "ロール", - "xpack.security.management.securityTitle": "セキュリティ", "xpack.security.management.users.confirmDelete.cancelButtonLabel": "キャンセル", "xpack.security.management.users.confirmDelete.confirmButtonLabel": "削除", "xpack.security.management.users.confirmDelete.deleteMultipleUsersTitle": "{userLength} ユーザーの削除", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b22b607f63aeb..954162647bf83 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2420,7 +2420,6 @@ "kibana-react.tableListView.listing.table.editActionDescription": "编辑", "kibana-react.tableListView.listing.table.editActionName": "编辑", "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "无法删除{entityName}", - "management.connectDataDisplayName": "连接数据", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", "indexPatternManagement.editIndexPattern.createIndex.defaultTypeName": "索引模式", @@ -4760,8 +4759,6 @@ "xpack.beatsManagement.breadcrumb.beatTags": "{beatId} 的 Beat 标记", "xpack.beatsManagement.breadcrumb.configurationTags": "配置标记", "xpack.beatsManagement.breadcrumb.enrolledBeats": "已注册 Beats", - "xpack.beatsManagement.centralManagementLinkLabel": "集中管理", - "xpack.beatsManagement.centralManagementSectionLabel": "Beats", "xpack.beatsManagement.config.other.error": "使用有效的 YAML 格式", "xpack.beatsManagement.config.otherConfigDescription": "使用 YAML 格式指定 Filebeat 输入的其他设置", "xpack.beatsManagement.config.otherConfigLabel": "其他配置", @@ -9981,7 +9978,6 @@ "xpack.ml.management.jobsList.noGrantedPrivilegesDescription": "您无权管理 ML 作业", "xpack.ml.management.jobsList.noPermissionToAccessLabel": "您需要访问 ML 作业的权限", "xpack.ml.management.jobsListTitle": "作业列表", - "xpack.ml.management.mlTitle": "Machine Learning", "xpack.ml.messagebarService.errorTitle": "发生了错误", "xpack.ml.models.jobService.allOtherRequestsCancelledDescription": " 所有其他请求已取消。", "xpack.ml.models.jobService.categorization.messages.failureToGetTokens": "无法对示例字段值样本进行分词。{message}", @@ -12834,7 +12830,6 @@ "xpack.security.management.roles.statusColumnName": "状态", "xpack.security.management.roles.subtitle": "将角色应用到用户组并管理整个堆栈的权限。", "xpack.security.management.rolesTitle": "角色", - "xpack.security.management.securityTitle": "安全性", "xpack.security.management.users.confirmDelete.cancelButtonLabel": "取消", "xpack.security.management.users.confirmDelete.confirmButtonLabel": "删除", "xpack.security.management.users.confirmDelete.deleteMultipleUsersTitle": "删除 {userLength} 用户", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 2f5172e8b386a..265cfddab4c06 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -7,7 +7,7 @@ export { BASE_ALERT_API_PATH } from '../../../../alerting/common'; export { BASE_ACTION_API_PATH } from '../../../../actions/common'; -export const BASE_PATH = '/management/kibana/triggersActions'; +export const BASE_PATH = '/management/insightsAndAlerting/triggersActions'; export type Section = 'connectors' | 'alerts'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx index 4f5007949f8b1..1da9abea40dba 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx @@ -127,7 +127,7 @@ describe('connector_add_flyout', () => { const manageLink = callout.find('EuiButton'); expect(manageLink).toHaveLength(1); expect(manageLink.getElements()[0].props.href).toMatchInlineSnapshot( - `"/app/kibana#/management/elasticsearch/license_management/"` + `"/app/kibana#/management/stack/license_management/"` ); const subscriptionLink = callout.find('EuiButtonEmpty'); diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 99a3d65589e8e..016b564c47d00 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -12,7 +12,7 @@ import { registerBuiltInAlertTypes } from './application/components/builtin_aler import { hasShowActionsCapability, hasShowAlertsCapability } from './application/lib/capabilities'; import { ActionTypeModel, AlertTypeModel } from './types'; import { TypeRegistry } from './application/type_registry'; -import { ManagementStart } from '../../../../src/plugins/management/public'; +import { ManagementStart, ManagementSectionId } from '../../../../src/plugins/management/public'; import { boot } from './application/boot'; import { ChartsPluginStart } from '../../../../src/plugins/charts/public'; import { PluginStartContract as AlertingStart } from '../../alerting/public'; @@ -73,12 +73,12 @@ export class Plugin // Don't register routes when user doesn't have access to the application if (canShowActions || canShowAlerts) { - plugins.management.sections.getSection('kibana')!.registerApp({ + plugins.management.sections.getSection(ManagementSectionId.InsightsAndAlerting).registerApp({ id: 'triggersActions', title: i18n.translate('xpack.triggersActionsUI.managementSection.displayName', { defaultMessage: 'Alerts and Actions', }), - order: 7, + order: 0, mount: params => { boot({ dataPlugin: plugins.data, diff --git a/x-pack/plugins/upgrade_assistant/public/plugin.ts b/x-pack/plugins/upgrade_assistant/public/plugin.ts index bb0f21062c151..be00a030d5a27 100644 --- a/x-pack/plugins/upgrade_assistant/public/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/public/plugin.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/public'; import { CloudSetup } from '../../cloud/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { NEXT_MAJOR_VERSION } from '../common/version'; import { Config } from '../common/config'; @@ -24,7 +24,7 @@ export class UpgradeAssistantUIPlugin implements Plugin { if (!enabled) { return; } - const appRegistrar = management.sections.getSection('elasticsearch')!; + const appRegistrar = management.sections.getSection(ManagementSectionId.Stack); const isCloudEnabled = Boolean(cloud?.isCloudEnabled); appRegistrar.registerApp({ @@ -33,7 +33,7 @@ export class UpgradeAssistantUIPlugin implements Plugin { defaultMessage: '{version} Upgrade Assistant', values: { version: `${NEXT_MAJOR_VERSION}.0` }, }), - order: 1000, + order: 1, async mount(params) { const { mountManagementSection } = await import('./application/mount_management_section'); return mountManagementSection(coreSetup, isCloudEnabled, params); diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap index fb40a42e47f75..5ae90a1613575 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap @@ -28,7 +28,7 @@ Array [

@@ -64,7 +64,7 @@ exports[`ShowLicenseInfo shallow renders without errors 1`] = `

Start free 14-day trial diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap index 3ee949b9712bd..df9de8c2ad03d 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap @@ -160,7 +160,7 @@ exports[`ML Flyout component shows license info if no ml available 1`] = `

diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx index fae81177a728c..33fbbb117a11a 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx @@ -22,7 +22,7 @@ export const ShowLicenseInfo = () => {

{labels.START_TRAIL_DESC}

{labels.START_TRAIL} diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx index cba96cd2fe5b1..0cdb3c0feb71f 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx @@ -61,7 +61,7 @@ export const ToggleAlertFlyoutButtonComponent: React.FC = ({ ', () => { expect(findTestSubject(idColumn, `watchIdColumn-${watch1.id}`).length).toBe(1); expect(findTestSubject(idColumn, `watchIdColumn-${watch1.id}`).props().href).toEqual( - `#/management/elasticsearch/watcher/watches/watch/${watch1.id}/status` + `#/management/insightsAndAlerting/watcher/watches/watch/${watch1.id}/status` ); }); diff --git a/x-pack/plugins/watcher/public/application/app.tsx b/x-pack/plugins/watcher/public/application/app.tsx index f4b9441719386..8a6d2746237e9 100644 --- a/x-pack/plugins/watcher/public/application/app.tsx +++ b/x-pack/plugins/watcher/public/application/app.tsx @@ -69,7 +69,7 @@ export const App = (deps: AppDeps) => { iconType="help" > {message}{' '} - + { values={{ watchName: watch.name, watchStatusLink: ( - + { return ( {id} @@ -326,7 +326,7 @@ export const WatchList = () => { )} iconType="pencil" color="primary" - href={`#/management/elasticsearch/watcher/watches/watch/${watch.id}/edit`} + href={`#/management/insightsAndAlerting/watcher/watches/watch/${watch.id}/edit`} data-test-subj="editWatchButton" /> diff --git a/x-pack/plugins/watcher/public/plugin.ts b/x-pack/plugins/watcher/public/plugin.ts index 6de21bc27d48c..6496c742fcb40 100644 --- a/x-pack/plugins/watcher/public/plugin.ts +++ b/x-pack/plugins/watcher/public/plugin.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin, CoreStart } from 'kibana/public'; import { first, map, skip } from 'rxjs/operators'; +import { ManagementSectionId } from '../../../../src/plugins/management/public'; import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; import { LicenseStatus } from '../common/types/license_status'; @@ -28,14 +29,15 @@ export class WatcherUIPlugin implements Plugin { { notifications, http, uiSettings, getStartServices }: CoreSetup, { licensing, management, data, home, charts }: Dependencies ) { - const esSection = management.sections.getSection('elasticsearch'); + const esSection = management.sections.getSection(ManagementSectionId.InsightsAndAlerting); - const watcherESApp = esSection!.registerApp({ + const watcherESApp = esSection.registerApp({ id: 'watcher', title: i18n.translate( 'xpack.watcher.sections.watchList.managementSection.watcherDisplayName', { defaultMessage: 'Watcher' } ), + order: 3, mount: async ({ element, setBreadcrumbs }) => { const [core] = await getStartServices(); const { i18n: i18nDep, docLinks, savedObjects } = core; @@ -74,7 +76,7 @@ export class WatcherUIPlugin implements Plugin { defaultMessage: 'Detect changes in your data by creating, managing, and monitoring alerts.', }), icon: 'watchesApp', - path: '/app/kibana#/management/elasticsearch/watcher/watches', + path: '/app/kibana#/management/insightsAndAlerting/watcher/watches', showOnHomePage: false, }; diff --git a/x-pack/test/functional/apps/reporting_management/report_delete_pagination.ts b/x-pack/test/functional/apps/reporting_management/report_delete_pagination.ts index d9d387f89640a..8c96fdbd0cbef 100644 --- a/x-pack/test/functional/apps/reporting_management/report_delete_pagination.ts +++ b/x-pack/test/functional/apps/reporting_management/report_delete_pagination.ts @@ -21,7 +21,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await security.testUser.setRoles(['kibana_admin', 'reporting_user']); await esArchiver.load('empty_kibana'); await esArchiver.load('reporting/archived_reports'); - await pageObjects.common.navigateToActualUrl('kibana', '/management/kibana/reporting'); + await pageObjects.common.navigateToApp('reporting'); await testSubjects.existOrFail('reportJobListing', { timeout: 200000 }); }); diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 0639782dc7f7b..94976511a1b12 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -114,7 +114,7 @@ export default async function({ readConfigFile }) { }, logstashPipelines: { pathname: '/app/kibana', - hash: '/management/logstash/pipelines', + hash: '/management/ingest/pipelines', }, maps: { pathname: '/app/maps', @@ -155,7 +155,7 @@ export default async function({ readConfigFile }) { }, rollupJob: { pathname: '/app/kibana', - hash: '/management/elasticsearch/rollup_jobs/', + hash: '/management/data/rollup_jobs/', }, apiKeys: { pathname: '/app/kibana', @@ -163,46 +163,46 @@ export default async function({ readConfigFile }) { }, licenseManagement: { pathname: '/app/kibana', - hash: '/management/elasticsearch/license_management', + hash: '/management/stack/license_management', }, indexManagement: { pathname: '/app/kibana', - hash: '/management/elasticsearch/index_management', + hash: '/management/data/index_management', }, indexLifecycleManagement: { pathname: '/app/kibana', - hash: '/management/elasticsearch/index_lifecycle_management', + hash: '/management/data/index_lifecycle_management', }, ingestPipelines: { pathname: '/app/kibana', - hash: '/management/elasticsearch/ingest_pipelines', + hash: '/management/ingest/ingest_pipelines', }, snapshotRestore: { pathname: '/app/kibana', - hash: '/management/elasticsearch/snapshot_restore', + hash: '/management/data/snapshot_restore', }, crossClusterReplication: { pathname: '/app/kibana', - hash: '/management/elasticsearch/cross_cluster_replication', + hash: '/management/data/cross_cluster_replication', }, remoteClusters: { pathname: '/app/kibana', - hash: '/management/elasticsearch/remote_clusters', + hash: '/management/data/remote_clusters', }, apm: { pathname: '/app/apm', }, watcher: { pathname: '/app/kibana', - hash: '/management/elasticsearch/watcher/watches/', + hash: '/management/insightsAndAlerting/watcher/watches/', }, transform: { pathname: '/app/kibana/', - hash: '/management/elasticsearch/transform', + hash: '/management/data/transform', }, reporting: { pathname: '/app/kibana/', - hash: '/management/kibana/reporting', + hash: '/management/insightsAndAlerting/reporting', }, }, diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts index 50de76d67e06b..f69c17c96f074 100644 --- a/x-pack/test/functional_with_es_ssl/config.ts +++ b/x-pack/test/functional_with_es_ssl/config.ts @@ -51,7 +51,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { ...xpackFunctionalConfig.get('apps'), triggersActions: { pathname: '/app/kibana', - hash: '/management/kibana/triggersActions', + hash: '/management/insightsAndAlerting/triggersActions', }, }, esTestCluster: { From 74611e742d6a805cff8dedb8f93e65b2f84cb10b Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Sat, 16 May 2020 09:04:51 +0200 Subject: [PATCH 09/18] Use ES API from start contract (#66157) * watcher uses es api from start * CCR uses ES API from start contract * Rollup uses ES API from start contract * Transform uses ES API from start contract * Snapshot_restore uses ES API from start contract * remove excessive logging. platform logs all the lifecycles * file uploader uses ES API from start contract * remove unnecessary async * use async getter * update rollup custom client usage * address cj comment * roll back changes. maps tests are failing Co-authored-by: Elastic Machine --- .../server/plugin.ts | 26 +++++++++++---- x-pack/plugins/rollup/server/plugin.ts | 32 ++++++++++++++----- .../plugins/snapshot_restore/server/plugin.ts | 26 +++++++++------ x-pack/plugins/transform/server/plugin.ts | 27 ++++++++++++---- x-pack/plugins/watcher/server/plugin.ts | 30 +++++++++++------ 5 files changed, 100 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/cross_cluster_replication/server/plugin.ts b/x-pack/plugins/cross_cluster_replication/server/plugin.ts index 25c99803480f3..7ef085a21ac1a 100644 --- a/x-pack/plugins/cross_cluster_replication/server/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/server/plugin.ts @@ -15,6 +15,7 @@ import { first } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import { CoreSetup, + ICustomClusterClient, Plugin, Logger, PluginInitializerContext, @@ -36,6 +37,13 @@ interface CrossClusterReplicationContext { client: IScopedClusterClient; } +async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { + const [core] = await getStartServices(); + // Extend the elasticsearchJs client with additional endpoints. + const esClientConfig = { plugins: [elasticsearchJsPlugin] }; + return core.elasticsearch.legacy.createClient('crossClusterReplication', esClientConfig); +} + const ccrDataEnricher = async (indicesList: Index[], callWithRequest: APICaller) => { if (!indicesList?.length) { return indicesList; @@ -69,6 +77,7 @@ export class CrossClusterReplicationServerPlugin implements Plugin; private readonly license: License; private readonly logger: Logger; + private ccrEsClient?: ICustomClusterClient; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); @@ -77,7 +86,7 @@ export class CrossClusterReplicationServerPlugin implements Plugin { + http.registerRouteHandlerContext('crossClusterReplication', async (ctx, request) => { + this.ccrEsClient = this.ccrEsClient ?? (await getCustomEsClient(getStartServices)); return { - client: ccrEsClient.asScoped(request), + client: this.ccrEsClient.asScoped(request), }; }); @@ -135,5 +142,10 @@ export class CrossClusterReplicationServerPlugin implements Plugin { private readonly logger: Logger; private readonly globalConfig$: Observable; private readonly license: License; + private rollupEsClient?: ICustomClusterClient; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); @@ -55,7 +63,7 @@ export class RollupPlugin implements Plugin { } public setup( - { http, uiSettings, elasticsearch }: CoreSetup, + { http, uiSettings, getStartServices }: CoreSetup, { licensing, indexManagement, visTypeTimeseries, usageCollection }: Dependencies ) { this.license.setup( @@ -72,12 +80,10 @@ export class RollupPlugin implements Plugin { } ); - // Extend the elasticsearchJs client with additional endpoints. - const esClientConfig = { plugins: [elasticsearchJsPlugin] }; - const rollupEsClient = elasticsearch.createClient('rollup', esClientConfig); - http.registerRouteHandlerContext('rollup', (context, request) => { + http.registerRouteHandlerContext('rollup', async (context, request) => { + this.rollupEsClient = this.rollupEsClient ?? (await getCustomEsClient(getStartServices)); return { - client: rollupEsClient.asScoped(request), + client: this.rollupEsClient.asScoped(request), }; }); @@ -116,7 +122,12 @@ export class RollupPlugin implements Plugin { const callWithRequestFactoryShim = ( elasticsearchServiceShim: CallWithRequestFactoryShim, request: KibanaRequest - ): APICaller => rollupEsClient.asScoped(request).callAsCurrentUser; + ): APICaller => { + return async (...args: Parameters) => { + this.rollupEsClient = this.rollupEsClient ?? (await getCustomEsClient(getStartServices)); + return await this.rollupEsClient.asScoped(request).callAsCurrentUser(...args); + }; + }; const { addSearchStrategy } = visTypeTimeseries; registerRollupSearchStrategy(callWithRequestFactoryShim, addSearchStrategy); @@ -140,5 +151,10 @@ export class RollupPlugin implements Plugin { } start() {} - stop() {} + + stop() { + if (this.rollupEsClient) { + this.rollupEsClient.close(); + } + } } diff --git a/x-pack/plugins/snapshot_restore/server/plugin.ts b/x-pack/plugins/snapshot_restore/server/plugin.ts index 00ff3db976d66..c5d3c665a3b7f 100644 --- a/x-pack/plugins/snapshot_restore/server/plugin.ts +++ b/x-pack/plugins/snapshot_restore/server/plugin.ts @@ -13,6 +13,7 @@ import { first } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import { CoreSetup, + ICustomClusterClient, Plugin, Logger, PluginInitializerContext, @@ -31,10 +32,17 @@ export interface SnapshotRestoreContext { client: IScopedClusterClient; } +async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { + const [core] = await getStartServices(); + const esClientConfig = { plugins: [elasticsearchJsPlugin] }; + return core.elasticsearch.legacy.createClient('snapshotRestore', esClientConfig); +} + export class SnapshotRestoreServerPlugin implements Plugin { private readonly logger: Logger; private readonly apiRoutes: ApiRoutes; private readonly license: License; + private snapshotRestoreESClient?: ICustomClusterClient; constructor(private context: PluginInitializerContext) { const { logger } = this.context; @@ -44,7 +52,7 @@ export class SnapshotRestoreServerPlugin implements Plugin } public async setup( - { http, elasticsearch }: CoreSetup, + { http, getStartServices }: CoreSetup, { licensing, security, cloud }: Dependencies ): Promise { const pluginConfig = await this.context.config @@ -72,11 +80,11 @@ export class SnapshotRestoreServerPlugin implements Plugin } ); - const esClientConfig = { plugins: [elasticsearchJsPlugin] }; - const snapshotRestoreESClient = elasticsearch.createClient('snapshotRestore', esClientConfig); - http.registerRouteHandlerContext('snapshotRestore', (ctx, request) => { + http.registerRouteHandlerContext('snapshotRestore', async (ctx, request) => { + this.snapshotRestoreESClient = + this.snapshotRestoreESClient ?? (await getCustomEsClient(getStartServices)); return { - client: snapshotRestoreESClient.asScoped(request), + client: this.snapshotRestoreESClient.asScoped(request), }; }); @@ -95,11 +103,11 @@ export class SnapshotRestoreServerPlugin implements Plugin }); } - public start() { - this.logger.debug('Starting plugin'); - } + public start() {} public stop() { - this.logger.debug('Stopping plugin'); + if (this.snapshotRestoreESClient) { + this.snapshotRestoreESClient.close(); + } } } diff --git a/x-pack/plugins/transform/server/plugin.ts b/x-pack/plugins/transform/server/plugin.ts index 7da991bc02b37..c8057a3e2fae1 100644 --- a/x-pack/plugins/transform/server/plugin.ts +++ b/x-pack/plugins/transform/server/plugin.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, + ICustomClusterClient, Plugin, IScopedClusterClient, Logger, @@ -38,10 +39,18 @@ const PLUGIN = { }), }; +async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { + const [core] = await getStartServices(); + return core.elasticsearch.legacy.createClient('transform', { + plugins: [elasticsearchJsPlugin], + }); +} + export class TransformServerPlugin implements Plugin<{}, void, any, any> { private readonly apiRoutes: ApiRoutes; private readonly license: License; private readonly logger: Logger; + private transformESClient?: ICustomClusterClient; constructor(initContext: PluginInitializerContext) { this.logger = initContext.logger.get(); @@ -49,7 +58,7 @@ export class TransformServerPlugin implements Plugin<{}, void, any, any> { this.license = new License(); } - setup({ elasticsearch, http }: CoreSetup, { licensing }: Dependencies): {} { + setup({ http, getStartServices }: CoreSetup, { licensing }: Dependencies): {} { const router = http.createRouter(); this.license.setup( @@ -72,12 +81,11 @@ export class TransformServerPlugin implements Plugin<{}, void, any, any> { }); // Can access via new platform router's handler function 'context' parameter - context.transform.client - const transformClient = elasticsearch.createClient('transform', { - plugins: [elasticsearchJsPlugin], - }); - http.registerRouteHandlerContext('transform', (context, request) => { + http.registerRouteHandlerContext('transform', async (context, request) => { + this.transformESClient = + this.transformESClient ?? (await getCustomEsClient(getStartServices)); return { - dataClient: transformClient.asScoped(request), + dataClient: this.transformESClient.asScoped(request), }; }); @@ -85,5 +93,10 @@ export class TransformServerPlugin implements Plugin<{}, void, any, any> { } start() {} - stop() {} + + stop() { + if (this.transformESClient) { + this.transformESClient.close(); + } + } } diff --git a/x-pack/plugins/watcher/server/plugin.ts b/x-pack/plugins/watcher/server/plugin.ts index 6a2e3b2e596b6..3f2891f919e37 100644 --- a/x-pack/plugins/watcher/server/plugin.ts +++ b/x-pack/plugins/watcher/server/plugin.ts @@ -12,6 +12,7 @@ declare module 'kibana/server' { import { CoreSetup, + ICustomClusterClient, IScopedClusterClient, Logger, Plugin, @@ -33,8 +34,15 @@ export interface WatcherContext { client: IScopedClusterClient; } +async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { + const [core] = await getStartServices(); + const esConfig = { plugins: [elasticsearchJsPlugin] }; + return core.elasticsearch.legacy.createClient('watcher', esConfig); +} + export class WatcherServerPlugin implements Plugin { - log: Logger; + private readonly log: Logger; + private watcherESClient?: ICustomClusterClient; private licenseStatus: LicenseStatus = { hasRequired: false, @@ -44,21 +52,17 @@ export class WatcherServerPlugin implements Plugin { this.log = ctx.logger.get(); } - async setup( - { http, elasticsearch: elasticsearchService }: CoreSetup, - { licensing }: Dependencies - ) { + async setup({ http, getStartServices }: CoreSetup, { licensing }: Dependencies) { const router = http.createRouter(); const routeDependencies: RouteDependencies = { router, getLicenseStatus: () => this.licenseStatus, }; - const config = { plugins: [elasticsearchJsPlugin] }; - const watcherESClient = elasticsearchService.createClient('watcher', config); - http.registerRouteHandlerContext('watcher', (ctx, request) => { + http.registerRouteHandlerContext('watcher', async (ctx, request) => { + this.watcherESClient = this.watcherESClient ?? (await getCustomEsClient(getStartServices)); return { - client: watcherESClient.asScoped(request), + client: this.watcherESClient.asScoped(request), }; }); @@ -89,6 +93,12 @@ export class WatcherServerPlugin implements Plugin { } }); } + start() {} - stop() {} + + stop() { + if (this.watcherESClient) { + this.watcherESClient.close(); + } + } } From 61936a1870977cb39d257a4964c86588cbb4416f Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Sat, 16 May 2020 09:08:45 +0200 Subject: [PATCH 10/18] [SOM] Preserve saved object references when saving the object (#66584) * create field for references and add comments * add FTR test * remove comments * address comments * use real reference in dataset and assert against it. --- .../public/lib/create_field_list.test.ts | 57 +++++++++++++++ .../public/lib/create_field_list.ts | 11 +-- .../object_view/components/field.tsx | 1 + .../object_view/components/form.tsx | 1 + .../edit_saved_object.ts | 69 +++++++++++++++++++ .../edit_saved_object/data.json | 9 ++- 6 files changed, 143 insertions(+), 5 deletions(-) diff --git a/src/plugins/saved_objects_management/public/lib/create_field_list.test.ts b/src/plugins/saved_objects_management/public/lib/create_field_list.test.ts index e7d6754ac4d05..7c447aefbc69a 100644 --- a/src/plugins/saved_objects_management/public/lib/create_field_list.test.ts +++ b/src/plugins/saved_objects_management/public/lib/create_field_list.test.ts @@ -59,6 +59,11 @@ describe('createFieldList', () => { "type": "boolean", "value": true, }, + Object { + "name": "references", + "type": "array", + "value": "[]", + }, ] `); }); @@ -76,6 +81,11 @@ describe('createFieldList', () => { \\"data\\": \\"value\\" }", }, + Object { + "name": "references", + "type": "array", + "value": "[]", + }, ] `); }); @@ -93,6 +103,48 @@ describe('createFieldList', () => { 1, 2, 3 + ]", + }, + Object { + "name": "references", + "type": "array", + "value": "[]", + }, + ] + `); + }); + + it(`generates a field for the object's references`, () => { + const obj = createObject( + { + someString: 'foo', + }, + [ + { id: 'ref1', type: 'type', name: 'Ref 1' }, + { id: 'ref12', type: 'other-type', name: 'Ref 2' }, + ] + ); + expect(createFieldList(obj)).toMatchInlineSnapshot(` + Array [ + Object { + "name": "someString", + "type": "text", + "value": "foo", + }, + Object { + "name": "references", + "type": "array", + "value": "[ + { + \\"id\\": \\"ref1\\", + \\"type\\": \\"type\\", + \\"name\\": \\"Ref 1\\" + }, + { + \\"id\\": \\"ref12\\", + \\"type\\": \\"other-type\\", + \\"name\\": \\"Ref 2\\" + } ]", }, ] @@ -126,6 +178,11 @@ describe('createFieldList', () => { "type": "text", "value": "B", }, + Object { + "name": "references", + "type": "array", + "value": "[]", + }, ] `); }); diff --git a/src/plugins/saved_objects_management/public/lib/create_field_list.ts b/src/plugins/saved_objects_management/public/lib/create_field_list.ts index 5d87c11a87198..d66d0b0a28844 100644 --- a/src/plugins/saved_objects_management/public/lib/create_field_list.ts +++ b/src/plugins/saved_objects_management/public/lib/create_field_list.ts @@ -29,12 +29,15 @@ export function createFieldList( object: SimpleSavedObject, service?: SavedObjectLoader ): ObjectField[] { - const fields = Object.entries(object.attributes as Record).reduce( + let fields = Object.entries(object.attributes as Record).reduce( (objFields, [key, value]) => { - return [...objFields, ...recursiveCreateFields(key, value)]; + return [...objFields, ...createFields(key, value)]; }, [] as ObjectField[] ); + // Special handling for references which isn't within "attributes" + fields = [...fields, ...createFields('references', object.references)]; + if (service && (service as any).Class) { addFieldsFromClass((service as any).Class, fields); } @@ -53,7 +56,7 @@ export function createFieldList( * @param {array} parents The parent keys to the field * @returns {array} */ -const recursiveCreateFields = (key: string, value: any, parents: string[] = []): ObjectField[] => { +const createFields = (key: string, value: any, parents: string[] = []): ObjectField[] => { const path = [...parents, key]; if (path.length > maxRecursiveIterations) { return []; @@ -78,7 +81,7 @@ const recursiveCreateFields = (key: string, value: any, parents: string[] = []): } else if (isPlainObject(field.value)) { let fields: ObjectField[] = []; forOwn(field.value, (childValue, childKey) => { - fields = [...fields, ...recursiveCreateFields(childKey as string, childValue, path)]; + fields = [...fields, ...createFields(childKey as string, childValue, path)]; }); return fields; } diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx index 1b69eb4240d68..afed6b492dc91 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx @@ -120,6 +120,7 @@ export class Field extends PureComponent { return (
{ set(source, field.name, value); }); + // we extract the `references` field that does not belong to attributes const { references, ...attributes } = source; await onSave({ attributes, references }); diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index 6af91ac9c5c94..1a85ff86498dc 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -26,6 +26,8 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'settings']); + const browser = getService('browser'); + const find = getService('find'); const setFieldValue = async (fieldName: string, value: string) => { return testSubjects.setValue(`savedObjects-editField-${fieldName}`, value); @@ -35,6 +37,26 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { return testSubjects.getAttribute(`savedObjects-editField-${fieldName}`, 'value'); }; + const setAceEditorFieldValue = async (fieldName: string, fieldValue: string) => { + const editorId = `savedObjects-editField-${fieldName}-aceEditor`; + await find.clickByCssSelector(`#${editorId}`); + return browser.execute( + (editor: string, value: string) => { + return (window as any).ace.edit(editor).setValue(value); + }, + editorId, + fieldValue + ); + }; + + const getAceEditorFieldValue = async (fieldName: string) => { + const editorId = `savedObjects-editField-${fieldName}-aceEditor`; + await find.clickByCssSelector(`#${editorId}`); + return browser.execute((editor: string) => { + return (window as any).ace.edit(editor).getValue() as string; + }, editorId); + }; + const focusAndClickButton = async (buttonSubject: string) => { const button = await testSubjects.find(buttonSubject); await button.scrollIntoViewIfNecessary(); @@ -99,5 +121,52 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { const objects = await PageObjects.settings.getSavedObjectsInTable(); expect(objects.includes('A Dashboard')).to.be(false); }); + + it('preserves the object references when saving', async () => { + const testVisualizationUrl = + '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed'; + const visualizationRefs = [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: 'logstash-*', + }, + ]; + + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + + const objects = await PageObjects.settings.getSavedObjectsInTable(); + expect(objects.includes('A Pie')).to.be(true); + + await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl); + + await testSubjects.existOrFail('savedObjectEditSave'); + + let displayedReferencesValue = await getAceEditorFieldValue('references'); + + expect(JSON.parse(displayedReferencesValue)).to.eql(visualizationRefs); + + await focusAndClickButton('savedObjectEditSave'); + + await PageObjects.settings.getSavedObjectsInTable(); + + await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl); + + // Parsing to avoid random keys ordering issues in raw string comparison + expect(JSON.parse(await getAceEditorFieldValue('references'))).to.eql(visualizationRefs); + + await setAceEditorFieldValue('references', JSON.stringify([], undefined, 2)); + + await focusAndClickButton('savedObjectEditSave'); + + await PageObjects.settings.getSavedObjectsInTable(); + + await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl); + + displayedReferencesValue = await getAceEditorFieldValue('references'); + + expect(JSON.parse(displayedReferencesValue)).to.eql([]); + }); }); } diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json index f085bad4c507e..cbaa0306f9a42 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json @@ -38,7 +38,14 @@ }, "type": "visualization", "updated_at": "2019-01-22T19:32:31.206Z" - } + }, + "references" : [ + { + "name" : "kibanaSavedObjectMeta.searchSourceJSON.index", + "type" : "index-pattern", + "id" : "logstash-*" + } + ] } } From 10ff9a90d0edd09ff0710c46152811904c498c5a Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Fri, 15 May 2020 13:14:58 -0400 Subject: [PATCH 11/18] Skip failing lens test(s). #66779 --- x-pack/test/functional/apps/lens/smokescreen.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 082008bccddd1..5dd9364c48fda 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -72,7 +72,8 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { .perform(); } - describe('lens smokescreen tests', () => { + // Failing: https://github.com/elastic/kibana/issues/66779 + describe.skip('lens smokescreen tests', () => { it('should allow editing saved visualizations', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens'); From a997d1fbf5e3e2f948c6cb5439367b72103ed7df Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 18 May 2020 10:12:04 +0200 Subject: [PATCH 12/18] [ML] fix url assertion (#66850) --- .../apis/ml/job_validation/validate.ts | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/x-pack/test/api_integration/apis/ml/job_validation/validate.ts b/x-pack/test/api_integration/apis/ml/job_validation/validate.ts index aaeead57345bc..9695c2b6892e0 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/validate.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/validate.ts @@ -6,6 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/machine_learning/security_common'; +import pkg from '../../../../../../package.json'; const COMMON_HEADERS = { 'kbn-xsrf': 'some-xsrf-token', @@ -74,15 +75,14 @@ export default ({ getService }: FtrProviderContext) => { heading: 'Job ID format is valid', text: 'Lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores, starts and ends with an alphanumeric character, and is no more than 64 characters long.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/master/ml-job-resource.html#ml-job-resource', + url: `https://www.elastic.co/guide/en/elasticsearch/reference/${pkg.branch}/ml-job-resource.html#ml-job-resource`, status: 'success', }, { id: 'detectors_function_not_empty', heading: 'Detector functions', text: 'Presence of detector functions validated in all detectors.', - url: 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#detectors', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#detectors`, status: 'success', }, { @@ -90,8 +90,7 @@ export default ({ getService }: FtrProviderContext) => { bucketSpan: '15m', heading: 'Bucket span', text: 'Format of "15m" is valid and passed validation checks.', - url: - 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#bucket-span', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#bucket-span`, status: 'success', }, { @@ -104,8 +103,7 @@ export default ({ getService }: FtrProviderContext) => { id: 'success_mml', heading: 'Model memory limit', text: 'Valid and within the estimated model memory limit.', - url: - 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#model-memory-limits', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#model-memory-limits`, status: 'success', }, ]); @@ -157,15 +155,14 @@ export default ({ getService }: FtrProviderContext) => { id: 'job_id_invalid', text: 'Job ID is invalid. It can contain lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores and must start and end with an alphanumeric character.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/master/ml-job-resource.html#ml-job-resource', + url: `https://www.elastic.co/guide/en/elasticsearch/reference/${pkg.branch}/ml-job-resource.html#ml-job-resource`, status: 'error', }, { id: 'detectors_function_not_empty', heading: 'Detector functions', text: 'Presence of detector functions validated in all detectors.', - url: 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#detectors', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#detectors`, status: 'success', }, { @@ -173,8 +170,7 @@ export default ({ getService }: FtrProviderContext) => { bucketSpan: '15m', heading: 'Bucket span', text: 'Format of "15m" is valid.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/master/ml-job-resource.html#ml-analysisconfig', + url: `https://www.elastic.co/guide/en/elasticsearch/reference/${pkg.branch}/ml-job-resource.html#ml-analysisconfig`, status: 'success', }, { @@ -239,15 +235,14 @@ export default ({ getService }: FtrProviderContext) => { heading: 'Job ID format is valid', text: 'Lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores, starts and ends with an alphanumeric character, and is no more than 64 characters long.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/master/ml-job-resource.html#ml-job-resource', + url: `https://www.elastic.co/guide/en/elasticsearch/reference/${pkg.branch}/ml-job-resource.html#ml-job-resource`, status: 'success', }, { id: 'detectors_function_not_empty', heading: 'Detector functions', text: 'Presence of detector functions validated in all detectors.', - url: 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#detectors', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#detectors`, status: 'success', }, { @@ -262,8 +257,7 @@ export default ({ getService }: FtrProviderContext) => { fieldName: 'order_id', text: 'Cardinality of partition_field "order_id" is above 1000 and might result in high memory usage.', - url: - 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#cardinality', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#cardinality`, status: 'warning', }, { @@ -271,8 +265,7 @@ export default ({ getService }: FtrProviderContext) => { heading: 'Bucket span', text: 'Bucket span is 1 day or more. Be aware that days are considered as UTC days, not local days.', - url: - 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#bucket-span', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#bucket-span`, status: 'info', }, { @@ -287,7 +280,7 @@ export default ({ getService }: FtrProviderContext) => { { id: 'success_influencers', text: 'Influencer configuration passed the validation checks.', - url: 'https://www.elastic.co/guide/en/machine-learning/master/ml-influencers.html', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/ml-influencers.html`, status: 'success', }, { @@ -295,8 +288,7 @@ export default ({ getService }: FtrProviderContext) => { mml: '1MB', text: 'The specified model memory limit is less than half of the estimated model memory limit and will likely hit the hard limit.', - url: - 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#model-memory-limits', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#model-memory-limits`, status: 'warning', }, ]); From 828108729a3879506055a6d70ad0dcd3006d53d5 Mon Sep 17 00:00:00 2001 From: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Date: Mon, 18 May 2020 12:53:00 +0300 Subject: [PATCH 13/18] Prevent further failures if timezone was not set (#66529) Co-authored-by: Elastic Machine --- .../apps/visualize/_point_series_options.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js index 17e0d1ca87fdd..cc25d10cf3257 100644 --- a/test/functional/apps/visualize/_point_series_options.js +++ b/test/functional/apps/visualize/_point_series_options.js @@ -341,6 +341,17 @@ export default function({ getService, getPageObjects }) { log.debug('close inspector'); await inspector.close(); }); + + after(async () => { + const timezone = await kibanaServer.uiSettings.get('dateFormat:tz'); + + // make sure the timezone was set to default correctly to avoid further failures + // for details see https://github.com/elastic/kibana/issues/63037 + if (timezone !== 'UTC') { + log.debug("set 'dateFormat:tz': 'UTC'"); + await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'UTC' }); + } + }); }); }); } From 404743a9ae723d42228ddbf2c5d1e576038d20fb Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 18 May 2020 13:29:26 +0200 Subject: [PATCH 14/18] stabilize listing page object (#66564) --- .../apps/dashboard/dashboard_clone.js | 19 ++------- .../apps/dashboard/dashboard_listing.js | 42 +++++-------------- .../apps/dashboard/dashboard_save.js | 20 ++------- .../apps/visualize/_visualize_listing.js | 29 ++++--------- test/functional/services/listing_table.ts | 29 ++++++++----- 5 files changed, 44 insertions(+), 95 deletions(-) diff --git a/test/functional/apps/dashboard/dashboard_clone.js b/test/functional/apps/dashboard/dashboard_clone.js index 8b7f6ba6a34dd..5d49e0cb97088 100644 --- a/test/functional/apps/dashboard/dashboard_clone.js +++ b/test/functional/apps/dashboard/dashboard_clone.js @@ -42,12 +42,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.clickClone(); await PageObjects.dashboard.confirmClone(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - clonedDashboardName - ); - - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', clonedDashboardName, 1); }); it('the copy should have all the same visualizations', async function() { @@ -75,11 +70,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.cancelClone(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 1); }); it('Clones on confirm duplicate title warning', async function() { @@ -92,11 +83,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.waitForRenderComplete(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName + ' Copy' - ); - expect(countOfDashboards).to.equal(2); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName + ' Copy', 2); }); }); } diff --git a/test/functional/apps/dashboard/dashboard_listing.js b/test/functional/apps/dashboard/dashboard_listing.js index e3e835109da2c..2ab2e107dae2d 100644 --- a/test/functional/apps/dashboard/dashboard_listing.js +++ b/test/functional/apps/dashboard/dashboard_listing.js @@ -42,11 +42,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.saveDashboard(dashboardName); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 1); }); it('is not shown when there is a dashboard', async function() { @@ -55,11 +51,7 @@ export default function({ getService, getPageObjects }) { }); it('is not shown when there are no dashboards shown during a search', async function() { - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - 'gobeldeguck' - ); - expect(countOfDashboards).to.equal(0); + await listingTable.searchAndExpectItemsCount('dashboard', 'gobeldeguck', 0); const promptExists = await PageObjects.dashboard.getCreateDashboardPromptExists(); expect(promptExists).to.be(false); @@ -78,11 +70,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.common.expectConfirmModalOpenState(false); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 1); }); it('succeeds on confirmation press', async function() { @@ -91,11 +79,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.common.clickConfirmOnModal(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(0); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 0); }); }); @@ -109,38 +93,32 @@ export default function({ getService, getPageObjects }) { it('matches on the first word', async function() { await listingTable.searchForItemWithName('Two'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(1); + await listingTable.expectItemsCount('dashboard', 1); }); it('matches the second word', async function() { await listingTable.searchForItemWithName('Words'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(1); + await listingTable.expectItemsCount('dashboard', 1); }); it('matches the second word prefix', async function() { await listingTable.searchForItemWithName('Wor'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(1); + await listingTable.expectItemsCount('dashboard', 1); }); it('does not match mid word', async function() { await listingTable.searchForItemWithName('ords'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(0); + await listingTable.expectItemsCount('dashboard', 0); }); it('is case insensitive', async function() { await listingTable.searchForItemWithName('two words'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(1); + await listingTable.expectItemsCount('dashboard', 1); }); it('is using AND operator', async function() { await listingTable.searchForItemWithName('three words'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(0); + await listingTable.expectItemsCount('dashboard', 0); }); }); diff --git a/test/functional/apps/dashboard/dashboard_save.js b/test/functional/apps/dashboard/dashboard_save.js index 7ffe951faa398..d0da033788689 100644 --- a/test/functional/apps/dashboard/dashboard_save.js +++ b/test/functional/apps/dashboard/dashboard_save.js @@ -17,8 +17,6 @@ * under the License. */ -import expect from '@kbn/expect'; - export default function({ getPageObjects, getService }) { const PageObjects = getPageObjects(['dashboard', 'header']); const listingTable = getService('listingTable'); @@ -50,11 +48,7 @@ export default function({ getPageObjects, getService }) { await PageObjects.dashboard.cancelSave(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 1); }); it('Saves on confirm duplicate title warning', async function() { @@ -73,11 +67,7 @@ export default function({ getPageObjects, getService }) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(2); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 2); }); it('Does not warn when you save an existing dashboard with the title it already has, and that title is a duplicate', async function() { @@ -128,11 +118,7 @@ export default function({ getPageObjects, getService }) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardNameEnterKey - ); - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardNameEnterKey, 1); }); }); } diff --git a/test/functional/apps/visualize/_visualize_listing.js b/test/functional/apps/visualize/_visualize_listing.js index e277c3c7d104d..dc07301a8ad50 100644 --- a/test/functional/apps/visualize/_visualize_listing.js +++ b/test/functional/apps/visualize/_visualize_listing.js @@ -17,8 +17,6 @@ * under the License. */ -import expect from '@kbn/expect'; - export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['visualize', 'visEditor']); const listingTable = getService('listingTable'); @@ -37,8 +35,7 @@ export default function({ getService, getPageObjects }) { // type markdown is used for simplicity await PageObjects.visualize.createSimpleMarkdownViz(vizName); await PageObjects.visualize.gotoVisualizationLandingPage(); - const visCount = await listingTable.getItemsCount('visualize'); - expect(visCount).to.equal(1); + await listingTable.expectItemsCount('visualize', 1); }); it('delete all viz', async function() { @@ -46,12 +43,10 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.createSimpleMarkdownViz(vizName + '2'); await PageObjects.visualize.gotoVisualizationLandingPage(); - let visCount = await listingTable.getItemsCount('visualize'); - expect(visCount).to.equal(3); + await listingTable.expectItemsCount('visualize', 3); await PageObjects.visualize.deleteAllVisualizations(); - visCount = await listingTable.getItemsCount('visualize'); - expect(visCount).to.equal(0); + await listingTable.expectItemsCount('visualize', 0); }); }); @@ -69,38 +64,32 @@ export default function({ getService, getPageObjects }) { it('matches on the first word', async function() { await listingTable.searchForItemWithName('Hello'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(1); + await listingTable.expectItemsCount('visualize', 1); }); it('matches the second word', async function() { await listingTable.searchForItemWithName('World'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(1); + await listingTable.expectItemsCount('visualize', 1); }); it('matches the second word prefix', async function() { await listingTable.searchForItemWithName('Wor'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(1); + await listingTable.expectItemsCount('visualize', 1); }); it('does not match mid word', async function() { await listingTable.searchForItemWithName('orld'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(0); + await listingTable.expectItemsCount('visualize', 0); }); it('is case insensitive', async function() { await listingTable.searchForItemWithName('hello world'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(1); + await listingTable.expectItemsCount('visualize', 1); }); it('is using AND operator', async function() { await listingTable.searchForItemWithName('hello banana'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(0); + await listingTable.expectItemsCount('visualize', 0); }); }); }); diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts index c7667ae7b4049..9a117458c7f76 100644 --- a/test/functional/services/listing_table.ts +++ b/test/functional/services/listing_table.ts @@ -17,6 +17,7 @@ * under the License. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; export function ListingTableProvider({ getService, getPageObjects }: FtrProviderContext) { @@ -85,11 +86,13 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider * Returns items count on landing page * @param appName 'visualize' | 'dashboard' */ - public async getItemsCount(appName: 'visualize' | 'dashboard'): Promise { - const elements = await find.allByCssSelector( - `[data-test-subj^="${prefixMap[appName]}ListingTitleLink"]` - ); - return elements.length; + public async expectItemsCount(appName: 'visualize' | 'dashboard', count: number) { + await retry.try(async () => { + const elements = await find.allByCssSelector( + `[data-test-subj^="${prefixMap[appName]}ListingTitleLink"]` + ); + expect(elements.length).to.equal(count); + }); } /** @@ -116,12 +119,18 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider * @param appName 'visualize' | 'dashboard' * @param name item name */ - public async searchAndGetItemsCount(appName: 'visualize' | 'dashboard', name: string) { + public async searchAndExpectItemsCount( + appName: 'visualize' | 'dashboard', + name: string, + count: number + ) { await this.searchForItemWithName(name); - const links = await testSubjects.findAll( - `${prefixMap[appName]}ListingTitleLink-${name.replace(/ /g, '-')}` - ); - return links.length; + await retry.try(async () => { + const links = await testSubjects.findAll( + `${prefixMap[appName]}ListingTitleLink-${name.replace(/ /g, '-')}` + ); + expect(links.length).to.equal(count); + }); } public async clickDeleteSelected() { From 1c7227933d010a6267130ef6b23ad2f9489410af Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 18 May 2020 16:23:17 +0200 Subject: [PATCH 15/18] [Uptime] Remove error while index pattern is loading (#66729) --- .../uptime/public/components/overview/kuery_bar/kuery_bar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx index 1ff823750a346..bd6fb4cf54b2b 100644 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx @@ -134,7 +134,7 @@ export function KueryBarComponent({ queryExample="" /> - {indexPatternMissing && ( + {indexPatternMissing && !loading && ( Date: Mon, 18 May 2020 17:29:24 +0200 Subject: [PATCH 16/18] [ML] Functional tests - stablize AD job creation checks (#66733) With this PR, anomaly detection jobs are checked to have a processed_record_count > 0 before waiting for a stopped datafeed. --- .../apis/ml/modules/setup_module.ts | 3 ++ .../services/machine_learning/api.ts | 42 ++++++++++++++++--- .../machine_learning/job_management.ts | 1 + 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index 39c87a91f0ccf..54ccbb5e0cbbd 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -236,6 +236,9 @@ export default ({ getService }: FtrProviderContext) => { const datafeedId = `datafeed-${job.jobId}`; await ml.api.waitForAnomalyDetectionJobToExist(job.jobId); await ml.api.waitForDatafeedToExist(datafeedId); + if (testData.requestBody.startDatafeed === true) { + await ml.api.waitForADJobRecordCountToBePositive(job.jobId); + } await ml.api.waitForJobState(job.jobId, job.jobState); await ml.api.waitForDatafeedState(datafeedId, job.datafeedState); } diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts index afc2567f3cce9..6fdc268810036 100644 --- a/x-pack/test/functional/services/machine_learning/api.ts +++ b/x-pack/test/functional/services/machine_learning/api.ts @@ -138,11 +138,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, async getJobState(jobId: string): Promise { - log.debug(`Fetching job state for job ${jobId}`); - const jobStats = await esSupertest - .get(`/_ml/anomaly_detectors/${jobId}/_stats`) - .expect(200) - .then((res: any) => res.body); + const jobStats = await this.getADJobStats(jobId); expect(jobStats.jobs).to.have.length(1); const state: JOB_STATE = jobStats.jobs[0].state; @@ -150,6 +146,16 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return state; }, + async getADJobStats(jobId: string): Promise { + log.debug(`Fetching anomaly detection job stats for job ${jobId}...`); + const jobStats = await esSupertest + .get(`/_ml/anomaly_detectors/${jobId}/_stats`) + .expect(200) + .then((res: any) => res.body); + + return jobStats; + }, + async waitForJobState(jobId: string, expectedJobState: JOB_STATE) { await retry.waitForWithTimeout( `job state to be ${expectedJobState}`, @@ -390,5 +396,31 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { await this.waitForDataFrameAnalyticsJobToExist(analyticsId); }, + + async getADJobRecordCount(jobId: string): Promise { + const jobStats = await this.getADJobStats(jobId); + + expect(jobStats.jobs).to.have.length(1); + const processedRecordCount: number = jobStats.jobs[0].data_counts.processed_record_count; + + return processedRecordCount; + }, + + async waitForADJobRecordCountToBePositive(jobId: string) { + await retry.waitForWithTimeout( + `'${jobId}' to have processed_record_count > 0`, + 10 * 1000, + async () => { + const processedRecordCount = await this.getADJobRecordCount(jobId); + if (processedRecordCount > 0) { + return true; + } else { + throw new Error( + `expected anomaly detection job '${jobId}' to have processed_record_count > 0 (got ${processedRecordCount})` + ); + } + } + ); + }, }; } diff --git a/x-pack/test/functional/services/machine_learning/job_management.ts b/x-pack/test/functional/services/machine_learning/job_management.ts index 8b85f85d46cf4..085bb31258012 100644 --- a/x-pack/test/functional/services/machine_learning/job_management.ts +++ b/x-pack/test/functional/services/machine_learning/job_management.ts @@ -43,6 +43,7 @@ export function MachineLearningJobManagementProvider( }, async waitForJobCompletion(jobId: string) { + await mlApi.waitForADJobRecordCountToBePositive(jobId); await mlApi.waitForDatafeedState(`datafeed-${jobId}`, DATAFEED_STATE.STOPPED); await mlApi.waitForJobState(jobId, JOB_STATE.CLOSED); }, From e5f56ad7a79b5df6fddf80f8c26cbaf59074cdac Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Mon, 18 May 2020 18:05:24 +0200 Subject: [PATCH 17/18] [RFC] Global search API (#64284) * initial file import * update types * update RFC PR number * first draft complete * draft v1.1.0 * initial self review * nits and comments * add 'preference' option * change meta type to Record * specify cancellation * remove generic for GlobalSearchResponse, use distinct type on client and server * use plain string instead of enum for GlobalSearchResult.type * remove terminology from unresolved questions * update pros/cons of core vs plugin * GS is exposed from a plugin instead of core * remove the server.publicAddress proposal, return mixed abs url / rel paths instead * distinguish result type between GS api and result providers * return batched results, no longer sort * remove request from GlobalSearchProviderContext * add maxResults to GlobalSearchProviderFindOptions * nit/typo --- rfcs/text/0011_global_search.md | 591 ++++++++++++++++++++++++++++++++ 1 file changed, 591 insertions(+) create mode 100644 rfcs/text/0011_global_search.md diff --git a/rfcs/text/0011_global_search.md b/rfcs/text/0011_global_search.md new file mode 100644 index 0000000000000..5ec368a1c2f02 --- /dev/null +++ b/rfcs/text/0011_global_search.md @@ -0,0 +1,591 @@ +- Start Date: 2020-04-19 +- RFC PR: [#64284](https://github.com/elastic/kibana/pull/64284) +- Kibana Issue: [#61657](https://github.com/elastic/kibana/issues/61657) + +# Summary + +A new Kibana plugin exposing an API on both public and server side, to allow consumers to search for various objects and +register result providers. + +Note: whether this will be an oss or xpack plugin still depends on https://github.com/elastic/dev/issues/1404. + +# Basic example + +- registering a result provider: + +```ts +setupDeps.globalSearch.registerResultProvider({ + id: 'my_provider', + find: (term, options, context) => { + const resultPromise = myService.search(term, context.core.savedObjects.client); + return from(resultPromise); + }, +}); +``` + +- using the `find` API from the client-side: + +```ts +startDeps.globalSearch.find('some term').subscribe( + ({ results }) => { + updateResults(results); + }, + () => {}, + () => { + showAsyncSearchIndicator(false); + } +); +``` + +# Motivation + +Kibana should do its best to assist users searching for and navigating to the various objects present on the Kibana platform. + +We should expose an API to make it possible for plugins to search for the various objects present on a Kibana instance. + +The first consumer of this API will be the global search bar [#57576](https://github.com/elastic/kibana/issues/57576). This API +should still be generic to answer similar needs from any other consumer, either client or server side. + +# Detailed design + +## API Design + +### Result provider API + +#### common types + +```ts +/** + * Static, non exhaustive list of the common search types. + * Only present to allow consumers and result providers to have aliases to the most common types. + */ +enum GlobalSearchCommonResultTypes { + application = 'application', + dashboard = 'dashboard', + visualization = 'visualization', + search = 'search', +} + +/** + * Options provided to {@link GlobalSearchResultProvider | result providers} `find` method. + */ +interface GlobalSearchProviderFindOptions { + /** + * A custom preference token associated with a search 'session' that should be used to get consistent scoring + * when performing calls to ES. Can also be used as a 'session' token for providers returning data from elsewhere + * than an elasticsearch cluster. + */ + preference: string; + /** + * Observable that emit once if and when the `find` call has been aborted by the consumer, or when the timeout period as been reached. + * When a `find` request is aborted, the service will stop emitting any new result to the consumer anyway, but + * this can (and should) be used to cancel any pending asynchronous task and complete the result observable. + */ + aborted$: Observable; + /** + * The total maximum number of results (including all batches / emissions) that should be returned by the provider for a given `find` request. + * Any result emitted exceeding this quota will be ignored by the service and not emitted to the consumer. + */ + maxResults: number; +} + +/** + * Representation of a result returned by a {@link GlobalSearchResultProvider | result provider} + */ +interface GlobalSearchProviderResult { + /** an id that should be unique for an individual provider's results */ + id: string; + /** the title/label of the result */ + title: string; + /** the type of result */ + type: string; + /** an optional EUI icon name to associate with the search result */ + icon?: string; + /** + * The url associated with this result. + * This can be either an absolute url, a path relative to the basePath, or a structure specifying if the basePath should be prepended. + * + * @example + * `result.url = 'https://kibana-instance:8080/base-path/app/my-app/my-result-type/id';` + * `result.url = '/app/my-app/my-result-type/id';` + * `result.url = { path: '/base-path/app/my-app/my-result-type/id', prependBasePath: false };` + */ + url: string | { path: string; prependBasePath: boolean }; + /** the score of the result, from 1 (lowest) to 100 (highest) */ + score: number; + /** an optional record of metadata for this result */ + meta?: Record; +} +``` + +Notes: + +- The `Serializable` type should be implemented and exposed from `core`. A basic implementation could be: + +```ts +type Serializable = string | number | boolean | PrimitiveArray | PrimitiveRecord; +interface PrimitiveArray extends Array {} +interface PrimitiveRecord extends Record {} +``` + +#### server + +```ts +/** + * Context passed to server-side {@GlobalSearchResultProvider | result provider}'s `find` method. + */ +export interface GlobalSearchProviderContext { + core: { + savedObjects: { + client: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; + }; + elasticsearch: { + legacy: { + client: IScopedClusterClient; + }; + }; + uiSettings: { + client: IUiSettingsClient; + }; + }; +} + +/** + * GlobalSearch result provider, to be registered using the {@link GlobalSearchSetup | global search API} + */ +type GlobalSearchResultProvider = { + id: string; + find( + term: string, + options: GlobalSearchProviderFindOptions, + context: GlobalSearchProviderContext + ): Observable; +}; +``` + +Notes: + +- Initial implementation will only provide a static / non extensible `GlobalSearchProviderContext` context. + It would be possible to allow plugins to register their own context providers as it's done for `RequestHandlerContext`, + but this will not be done until the need arises. +- The performing `request` object could also be exposed on the context to allow result providers + to scope their custom services if needed. However as the previous option, this should only be done once needed. + +#### public + +```ts +/** + * GlobalSearch result provider, to be registered using the {@link GlobalSearchSetup | global search API} + */ +type GlobalSearchResultProvider = { + id: string; + find( + term: string, + options: GlobalSearchProviderFindOptions + ): Observable; +}; +``` + +Notes: + +- The client-side version of `GlobalSearchResultProvider` is slightly different than the + server one, as there is no `context` parameter on the `find` signature. + +### Plugin API + +#### server API + +```ts +/** + * Representation of a result returned by the {@link GlobalSearchPluginStart.find | `find` API} + */ +type GlobalSearchResult = Omit & { + /** + * The url associated with this result. + * This can be either an absolute url, or a relative path including the basePath + */ + url: string; +}; + +/** + * Options for the server-side {@link GlobalSearchServiceStart.find | find API} + */ +interface GlobalSearchFindOptions { + /** + * a custom preference token associated with a search 'session' that should be used to get consistent scoring + * when performing calls to ES. Can also be used as a 'session' token for providers returning data from elsewhere + * than an elasticsearch cluster. + * If not specified, a random token will be generated and used when callingn the underlying result providers. + */ + preference?: string; + /** + * Optional observable to notify that the associated `find` call should be canceled. + * If/when provided and emitting, the result observable will be completed and no further result emission will be performed. + */ + aborted$?: Observable; +} + +/** + * Response returned from the server-side {@link GlobalSearchServiceStart | global search service}'s `find` API + */ +type GlobalSearchBatchedResults = { + /** + * Results for this batch + */ + results: GlobalSearchResult[]; +}; + +/** @public */ +interface GlobalSearchPluginSetup { + registerResultProvider(provider: GlobalSearchResultProvider); +} + +/** @public */ +interface GlobalSearchPluginStart { + find( + term: string, + options: GlobalSearchFindOptions, + request: KibanaRequest + ): Observable; +} +``` + +#### public API + +```ts +/** + * Options for the client-side {@link GlobalSearchServiceStart.find | find API} + */ +interface GlobalSearchFindOptions { + /** + * Optional observable to notify that the associated `find` call should be canceled. + * If/when provided and emitting, the result observable will be completed and no further result emission will be performed. + */ + aborted$?: Observable; +} + +/** + * Enhanced {@link GlobalSearchResult | result type} for the client-side, + * to allow navigating to a given result. + */ +interface NavigableGlobalSearchResult extends GlobalSearchResult { + /** + * Navigate to this result's associated url. If the result is on this kibana instance, user will be redirected to it + * in a SPA friendly way using `application.navigateToApp`, else, a full page refresh will be performed. + */ + navigate: () => Promise; +} + +/** + * Response returned from the client-side {@link GlobalSearchServiceStart | global search service}'s `find` API + */ +type GlobalSearchBatchedResults = { + /** + * Results for this batch + */ + results: NavigableGlobalSearchResult[]; +}; + +/** @public */ +interface GlobalSearchPluginSetup { + registerResultProvider(provider: GlobalSearchResultProvider); +} + +/** @public */ +interface GlobalSearchPluginStart { + find(term: string, options: GlobalSearchFindOptions): Observable; +} +``` + +Notes: + +- The public API is very similar to its server counterpart. The differences are: + - The `registerResultProvider` setup APIs share the same signature, however the input `GlobalSearchResultProvider` + types are different on the client and server. + - The `find` start API signature got a `KibanaRequest` for `server`, when this parameter is not present for `public`. +- The `find` API returns a observable of `NavigableGlobalSearchResult` instead of plain `GlobalSearchResult`. This type + is here to enhance results with a `navigate` method to let the `GlobalSearch` plugin handle the navigation logic, which is + non-trivial. See the [Redirecting to a result](#redirecting-to-a-result) section for more info. + +#### http API + +An internal HTTP API will be exposed on `/internal/global_search/find` to allow the client-side `GlobalSearch` plugin +to fetch results from the server-side result providers. + +It should be very close to: + +```ts +router.post( + { + path: '/internal/global_search/find', + validate: { + body: schema.object({ + term: schema.string(), + options: schema.maybe( + schema.object({ + preference: schema.maybe(schema.string()), + }) + ), + }), + }, + }, + async (ctx, req, res) => { + const { term, options } = req.body; + const results = await ctx.globalSearch + .find(term, { ...options, $aborted: req.events.aborted$ }) + .pipe(reduce((acc, results) => [...acc, ...results])) + .toPromise(); + return res.ok({ + body: { + results, + }, + }); + } +); +``` + +Notes: + +- This API is only for internal use and communication between the client and the server parts of the `GS` API. When + the need to expose an API for external consumers will appear, a new public API will be exposed for that. +- A new `globalSearch` context will be exposed on core's `RequestHandlerContext` to wrap a `find` call with current request. +- Example implementation is awaiting for all results and then returns them as a single response. Ideally, we would + leverage the `bfetch` plugin to stream the results to the client instead. + +## Functional behavior + +### summary + +- the `GlobalSearch` plugin setup contract exposes an API to be able to register result providers (`GlobalSearchResultProvider`). + These providers can be registered from either public or server side, even if the interface for each side is not + exactly the same. +- the `GlobalSearch` plugin start contract exposes an API to be able to search for objects. This API is available from both public + and server sides. + - When using the server `find` API, only results from providers registered from the server will be returned. + - When using the public `find` API, results from provider registered from both server and public sides will be returned. +- During a `find` call, the service will call all the registered result providers and collect their result observables. + Every time a result provider emits some new results, the `globalSearch` service will: + - process them to convert their url to the expected output format + - emit the processed results + +### result provider registration + +Due to the fact that some kind of results (i.e `application`, and maybe later `management_section`) only exists on +the public side of Kibana and therefor are not known on the server side, the `registerResultProvider` API will be +available both from the public and the server counterpart of the `GlobalSearchPluginSetup` contract. + +However, as results from providers registered from the client-side will not be available from the server's `find` API, +registering result providers from the client should only be done to answer this specific use case and will be +discouraged, by providing appropriated jsdoc and documentation explaining that it should only +be used when it is not technically possible to register it from the server side instead. + +### results url processing + +When retrieving results from providers, the GS service will convert them from the provider's `GlobalSearchProviderResult` +result type to `GlobalSeachResult`, which is the structure returned from the `GlobalSearchPluginStart.find` observable. + +In current specification, the only conversion step is to transform the `result.url` property following this logic: + +- if `url` is an absolute url, it will not be modified +- if `url` is a relative path, the basePath will be prepended using `basePath.prepend` +- if `url` is a `{ path: string; prependBasePath: boolean }` structure: + - if `prependBasePath` is true, the basePath will be prepended to the given `path` using `basePath.prepend` + - if `prependBasePath` is false, the given `path` will be returned unmodified + +#### redirecting to a result + +Parsing a relative or absolute result url to perform SPA navigation can be non trivial, and should remains the responsibility +of the GlobalSearch plugin API. + +This is why `NavigableGlobalSearchResult.navigate` has been introduced on the client-side version of the `find` API + +When using `navigate` from a result instance, the following logic will be executed: + +If all these criteria are true for `result.url`: + +- (only for absolute URLs) The origin of the URL matches the origin of the browser's current location +- The pathname of the URL starts with the current basePath (eg. /mybasepath/s/my-space) +- The pathname segment after the basePath matches any known application route (eg. /app// or any application's `appRoute` configuration) + +Then: match the pathname segment to the corresponding application and do the SPA navigation to that application using +`application.navigateToApp` using the remaining pathname segment for the `path` option. + +Otherwise: do a full page navigation using `window.location.assign` + +### searching from the server side + +When calling `GlobalSearchPluginStart.find` from the server-side service: + +- the service will call `find` on each server-side registered result provider and collect the resulting result observables + +- then, the service will merge every result observable and trigger the next step on every emission until either + - A predefined timeout duration is reached + - All result observables are completed + +- on every emission of the merged observable, the results will be processed then emitted. + +A very naive implementation of this behavior would be: + +```ts +search( + term: string, + options: GlobalSearchFindOptions, + request: KibanaRequest +): Observable { + const aborted$ = merge(timeout$, options.$aborted).pipe(first()) + const fromProviders$ = this.providers.map(p => + p.find(term, { ...options, aborted$ }, contextFromRequest(request)) + ); + return merge([...fromProviders$]).pipe( + takeUntil(aborted$), + map(newResults => { + return process(newResults); + }), + ); +} +``` + +### searching from the client side + +When calling `GlobalSearchPluginStart.find` from the public-side service: + +- The service will call: + + - the server-side API via an http call to fetch results from the server-side result providers + - `find` on each client-side registered result provider and collect the resulting observables + +- Then, the service will merge every result observable and trigger the next step on every emission until either + + - A predefined timeout duration is reached + - All result observables are completed + +- on every emission of the merged observable, the results will be processed then emitted. + +A very naive implementation of this behavior would be: + +``` +search( + term: string, + options: GlobalSearchFindOptions, +): Observable { + const aborted$ = merge(timeout$, options.$aborted).pipe(first()) + const fromProviders$ = this.providers.map(p => + p.find(term, { ...options, aborted$ }) + ); + const fromServer$ = of(this.fetchServerResults(term, options, aborted$)) + return merge([...fromProviders$, fromServer$]).pipe( + takeUntil(aborted$), + map(newResults => { + return process(newResults); + }), + ); +} +``` + +Notes: + +- The example implementation is not streaming results from the server, meaning that all results from server-side + registered providers will all be fetched and emitted in a single batch. Ideally, we would leverage the `bfetch` plugin + to stream the results to the client instead. + +### results sorting + +As the GS `find` API is 'streaming' the results from the result providers by emitting the results in batches, sorting results in +each individual batch, even if technically possible, wouldn't provide much value as the consumer will need to sort the +aggregated results on each emission anyway. This is why the results emitted by the `find` API should be considered as +unsorted. Consumers should implement sorting themselves, using either the `score` attribute, or any other arbitrary logic. + +#### Note on score value + +Due to the fact that the results will be coming from various providers, from multiple ES queries or even not from ES, +using a centralized scoring mechanism is not possible. + +the `GlobalSearchResult` contains a `score` field, with an expected value going from 1 (lowest) to 100 (highest). +How this field is populated from each individual provider is considered an implementation detail. + +### Search cancellation + +Consumers can cancel a `find` call at any time by providing a cancellation observable with +the `GlobalSearchFindOptions.aborted$` option and then emitting from it. + +When this observable is provided and emitting, the GS service will complete the result observable. + +This observable will also be passed down to the underlying result providers, that can leverage it to cancel any pending +asynchronous task and perform cleanup if necessary. + +# Drawbacks + +See alternatives. + +# Alternatives + +## Result providers could be only registrable from the server-side API + +The fact that some kinds of results, and therefore some result providers, must be on the client-side makes the API more complex, +while making these results not available from the server-side and HTTP APIs. + +We could decide to only allow providers registration from the server-side. It would reduce API exposure, while simplifying +the service implementation. However to do that, we would need to find a solution to be able to implement a server-side +result provider for `application` (and later `management_section`) type provider. + +I will directly exclude the option to move the `application` registration (`core.application.register`) from client +to server-side, as it's a very heavy impacting (and breaking) change to `core` APIs that would requires more reasons +than just this RFC/API to consider. + +### AST parsing + +One option to make the `application` results 'visible' from the server-side would be to parse the client code at build time +using AST to find all usages to `application.register` inspect the parameters, and generates a server file +containing the applications. The server-side `application` result provider would then just read this file and uses it +to return application results. + +However + +- At the parsing would be done at build time, we would not be able to generate entries for any 3rd party plugins +- As entries for every existing applications would be generated, the search provider would to be able to know which + applications are actually enabled/accessible at runtime to filter them, which is all but easy +- It will also not contains test plugin apps, making it really hard to FTR +- AST parsing is a complex mechanism for an already unsatisfactory alternative + +### Duplicated server-side `application.register` API + +One other option would be to duplicate the `application.register` API on the server side, with a subset of the +client-side metadata. + +```ts +core.application.register({ + id: 'app_status', + title: 'App Status', + euiIconType: 'snowflake', +}); +``` + +This way, the applications could be searchable from the server using this server-side `applications` registry. + +However + +- It forces plugin developers to add this API call. In addition to be a very poor developer experience, it can also + very easily be forgotten, making a given app non searchable +- client-side only plugins would need to add a server-side part to their plugin just to register their application on + the server side + +# Adoption strategy + +The `globalSearch` service is a new feature provided by the `core` API. Also, the base providers +used to search for saved objects and applications will be implemented by the platform team, meaning +that by default, plugin developers won't have to do anything. + +Plugins that wish to expose additional result providers will easily be able to do so by using the exposed APIs and +documentation. + +# How we teach this + +This follows the same patterns we have used for other Core APIs: Observables subscriptions, etc. + +This should be taught using the same channels we've leveraged for other Kibana Platform APIs, API documentation and +example plugins. + +# Unresolved questions + +N/A From 3b4814ba48f0088ed62ded231d255a9bfe4fa40e Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Mon, 18 May 2020 12:48:50 -0400 Subject: [PATCH 18/18] dont hide errors (#66764) Co-authored-by: Elastic Machine --- src/core/public/application/ui/app_container.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx index aad7e6dcf270a..4317ede547202 100644 --- a/src/core/public/application/ui/app_container.tsx +++ b/src/core/public/application/ui/app_container.tsx @@ -87,6 +87,8 @@ export const AppContainer: FunctionComponent = ({ })) || null; } catch (e) { // TODO: add error UI + // eslint-disable-next-line no-console + console.error(e); } finally { setShowSpinner(false); setIsMounting(false);